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,567 @@
|
|
|
1
|
+
#include "librats/subsystems/pubsub.h"
|
|
2
|
+
#include "librats/node/node_context.h"
|
|
3
|
+
#include "librats/util/logger.h"
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <random>
|
|
7
|
+
#include <tuple>
|
|
8
|
+
|
|
9
|
+
namespace librats {
|
|
10
|
+
|
|
11
|
+
namespace {
|
|
12
|
+
|
|
13
|
+
// Wire ops (see the format table in pubsub.h).
|
|
14
|
+
enum : uint8_t {
|
|
15
|
+
OP_PUBLISH = 0,
|
|
16
|
+
OP_SUBSCRIBE = 1,
|
|
17
|
+
OP_UNSUBSCRIBE = 2,
|
|
18
|
+
OP_GRAFT = 3,
|
|
19
|
+
OP_PRUNE = 4,
|
|
20
|
+
OP_IHAVE = 5,
|
|
21
|
+
OP_IWANT = 6,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
constexpr size_t kIdSize = PeerId::kSize + 8; // origin(32) || seqno(u64) = 40 bytes
|
|
25
|
+
|
|
26
|
+
void put_u16(Bytes& b, uint16_t v) { b.push_back(v >> 8); b.push_back(v & 0xFF); }
|
|
27
|
+
void put_u64(Bytes& b, uint64_t v) { for (int i = 7; i >= 0; --i) b.push_back((v >> (i * 8)) & 0xFF); }
|
|
28
|
+
void put_topic(Bytes& b, const std::string& t) {
|
|
29
|
+
put_u16(b, static_cast<uint16_t>(t.size()));
|
|
30
|
+
b.insert(b.end(), t.begin(), t.end());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// The dedup / cache key for a message: its origin id followed by the origin's
|
|
34
|
+
/// per-message sequence number. Globally unique without a clock or hash.
|
|
35
|
+
std::string make_id(const PeerId& origin, uint64_t seqno) {
|
|
36
|
+
std::string id(reinterpret_cast<const char*>(origin.bytes().data()), PeerId::kSize);
|
|
37
|
+
for (int i = 7; i >= 0; --i) id.push_back(static_cast<char>((seqno >> (i * 8)) & 0xFF));
|
|
38
|
+
return id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
struct Reader {
|
|
42
|
+
const uint8_t* p;
|
|
43
|
+
const uint8_t* end;
|
|
44
|
+
bool ok = true;
|
|
45
|
+
|
|
46
|
+
uint8_t u8() { if (p >= end) { ok = false; return 0; } return *p++; }
|
|
47
|
+
uint16_t u16() { if (end - p < 2) { ok = false; return 0; } uint16_t v = (uint16_t(p[0]) << 8) | p[1]; p += 2; return v; }
|
|
48
|
+
uint64_t u64() { if (end - p < 8) { ok = false; return 0; } uint64_t v = 0; for (int i = 0; i < 8; ++i) v = (v << 8) | *p++; return v; }
|
|
49
|
+
ByteView bytes(size_t n) { if (size_t(end - p) < n) { ok = false; return {}; } ByteView v(p, n); p += n; return v; }
|
|
50
|
+
ByteView rest() { ByteView v(p, size_t(end - p)); p = end; return v; }
|
|
51
|
+
std::string str(size_t n) { ByteView v = bytes(n); return ok ? std::string(reinterpret_cast<const char*>(v.data()), v.size()) : std::string(); }
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Frame builders.
|
|
55
|
+
Bytes build_publish(const PeerId& origin, uint64_t seqno, const std::string& topic, ByteView data) {
|
|
56
|
+
Bytes f;
|
|
57
|
+
f.push_back(OP_PUBLISH);
|
|
58
|
+
f.insert(f.end(), origin.bytes().begin(), origin.bytes().end());
|
|
59
|
+
put_u64(f, seqno);
|
|
60
|
+
put_topic(f, topic);
|
|
61
|
+
f.insert(f.end(), data.begin(), data.end());
|
|
62
|
+
return f;
|
|
63
|
+
}
|
|
64
|
+
Bytes build_ctrl(uint8_t op, const std::string& topic) {
|
|
65
|
+
Bytes f;
|
|
66
|
+
f.push_back(op);
|
|
67
|
+
put_topic(f, topic);
|
|
68
|
+
return f;
|
|
69
|
+
}
|
|
70
|
+
Bytes build_ihave(const std::string& topic, const std::vector<std::string>& ids) {
|
|
71
|
+
Bytes f;
|
|
72
|
+
f.push_back(OP_IHAVE);
|
|
73
|
+
put_topic(f, topic);
|
|
74
|
+
put_u16(f, static_cast<uint16_t>(std::min<size_t>(ids.size(), 0xFFFF)));
|
|
75
|
+
for (const std::string& id : ids) { if (id.size() == kIdSize) f.insert(f.end(), id.begin(), id.end()); }
|
|
76
|
+
return f;
|
|
77
|
+
}
|
|
78
|
+
Bytes build_iwant(const std::vector<std::string>& ids) {
|
|
79
|
+
Bytes f;
|
|
80
|
+
f.push_back(OP_IWANT);
|
|
81
|
+
put_u16(f, static_cast<uint16_t>(std::min<size_t>(ids.size(), 0xFFFF)));
|
|
82
|
+
for (const std::string& id : ids) { if (id.size() == kIdSize) f.insert(f.end(), id.begin(), id.end()); }
|
|
83
|
+
return f;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
} // namespace
|
|
87
|
+
|
|
88
|
+
// ── Construction ──────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
PubSub::PubSub() : PubSub(Config{}) {}
|
|
91
|
+
|
|
92
|
+
PubSub::PubSub(Config config) : config_(config) {
|
|
93
|
+
std::random_device rd;
|
|
94
|
+
rng_.seed(rd());
|
|
95
|
+
// Seed the sequence counter randomly so a restarted node does not reuse the
|
|
96
|
+
// (origin, seqno) ids a previous instance with the same PeerId already burned.
|
|
97
|
+
seqno_ = (static_cast<uint64_t>(rd()) << 32) ^ rd();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
PubSub::~PubSub() { stop(); }
|
|
101
|
+
|
|
102
|
+
// ── Subsystem lifecycle ───────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
void PubSub::attach(NodeContext& ctx) {
|
|
105
|
+
network_ = &ctx.network;
|
|
106
|
+
network_->on(MessageType::Gossip,
|
|
107
|
+
[this](const Peer& peer, ByteView payload) { on_gossip(peer, payload); });
|
|
108
|
+
network_->on_peer_connected([this](const Peer& peer) { on_new_peer(peer); });
|
|
109
|
+
network_->on_peer_disconnected([this](const PeerId& id) { on_peer_gone(id); });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
void PubSub::start() {
|
|
113
|
+
if (running_.exchange(true)) return;
|
|
114
|
+
{
|
|
115
|
+
std::lock_guard<std::mutex> lock(mcache_mutex_);
|
|
116
|
+
if (history_.empty()) history_.push_front({}); // current gossip window
|
|
117
|
+
}
|
|
118
|
+
heartbeat_thread_ = std::thread(&PubSub::heartbeat_loop, this);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
void PubSub::stop() {
|
|
122
|
+
if (!running_.exchange(false)) return;
|
|
123
|
+
hb_cv_.notify_all();
|
|
124
|
+
if (heartbeat_thread_.joinable()) heartbeat_thread_.join();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Local subscription API ────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
void PubSub::subscribe(const std::string& topic, Handler handler) {
|
|
130
|
+
bool is_new;
|
|
131
|
+
CtrlList grafts, prunes;
|
|
132
|
+
{
|
|
133
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
134
|
+
is_new = subscriptions_.find(topic) == subscriptions_.end();
|
|
135
|
+
subscriptions_[topic] = std::move(handler);
|
|
136
|
+
topics_[topic]; // ensure peering state exists
|
|
137
|
+
if (is_new) maintain_mesh_locked(topic, grafts, prunes); // graft from peers already known
|
|
138
|
+
}
|
|
139
|
+
if (is_new && network_) {
|
|
140
|
+
broadcast_ctrl(OP_SUBSCRIBE, topic);
|
|
141
|
+
for (const auto& g : grafts) send_ctrl(g.first, OP_GRAFT, g.second);
|
|
142
|
+
for (const auto& p : prunes) send_ctrl(p.first, OP_PRUNE, p.second);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
void PubSub::unsubscribe(const std::string& topic) {
|
|
147
|
+
std::vector<PeerId> mesh_to_prune;
|
|
148
|
+
bool removed;
|
|
149
|
+
{
|
|
150
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
151
|
+
removed = subscriptions_.erase(topic) > 0;
|
|
152
|
+
auto it = topics_.find(topic);
|
|
153
|
+
if (removed && it != topics_.end()) {
|
|
154
|
+
mesh_to_prune.assign(it->second.mesh.begin(), it->second.mesh.end());
|
|
155
|
+
it->second.mesh.clear();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (removed && network_) {
|
|
159
|
+
broadcast_ctrl(OP_UNSUBSCRIBE, topic);
|
|
160
|
+
for (const PeerId& p : mesh_to_prune) send_ctrl(p, OP_PRUNE, topic);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
void PubSub::publish(const std::string& topic, ByteView data) {
|
|
165
|
+
if (!network_) return;
|
|
166
|
+
|
|
167
|
+
const PeerId origin = network_->local_id();
|
|
168
|
+
uint64_t seqno;
|
|
169
|
+
std::vector<PeerId> targets;
|
|
170
|
+
bool subscribed;
|
|
171
|
+
{
|
|
172
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
173
|
+
seqno = seqno_++;
|
|
174
|
+
subscribed = subscriptions_.count(topic) > 0;
|
|
175
|
+
Topic& t = topics_[topic];
|
|
176
|
+
if (subscribed) {
|
|
177
|
+
targets.assign(t.mesh.begin(), t.mesh.end()); // eager push along the mesh
|
|
178
|
+
} else {
|
|
179
|
+
// Fanout: top up to fanout_size from known subscribers, then publish to them.
|
|
180
|
+
if (static_cast<int>(t.fanout.size()) < config_.fanout_size) {
|
|
181
|
+
std::vector<PeerId> cands;
|
|
182
|
+
for (const PeerId& p : t.subscribers)
|
|
183
|
+
if (!t.fanout.count(p)) cands.push_back(p);
|
|
184
|
+
for (const PeerId& p : random_sample(std::move(cands),
|
|
185
|
+
config_.fanout_size - static_cast<int>(t.fanout.size())))
|
|
186
|
+
t.fanout.insert(p);
|
|
187
|
+
}
|
|
188
|
+
t.last_fanout = std::chrono::steady_clock::now();
|
|
189
|
+
targets.assign(t.fanout.begin(), t.fanout.end());
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const std::string id = make_id(origin, seqno);
|
|
194
|
+
const Bytes frame = build_publish(origin, seqno, topic, data);
|
|
195
|
+
mark_seen(id); // so an echo of our own message is dropped
|
|
196
|
+
cache_message(id, topic, frame); // keep content so we can answer IWANT
|
|
197
|
+
deliver_local(origin, topic, data); // a subscribed publisher hears itself
|
|
198
|
+
|
|
199
|
+
for (const PeerId& t : targets) network_->send(t, MessageType::Gossip, ByteView(frame));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Inbound dispatch ──────────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
void PubSub::on_gossip(const Peer& peer, ByteView payload) {
|
|
205
|
+
Reader r{payload.data(), payload.data() + payload.size()};
|
|
206
|
+
const uint8_t op = r.u8();
|
|
207
|
+
const PeerId& from = peer.id();
|
|
208
|
+
|
|
209
|
+
switch (op) {
|
|
210
|
+
case OP_SUBSCRIBE:
|
|
211
|
+
case OP_UNSUBSCRIBE: {
|
|
212
|
+
std::string topic = r.str(r.u16());
|
|
213
|
+
if (r.ok && !topic.empty()) recv_subscription(from, topic, op == OP_SUBSCRIBE);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
case OP_GRAFT: {
|
|
217
|
+
std::string topic = r.str(r.u16());
|
|
218
|
+
if (r.ok && !topic.empty()) recv_graft(from, topic);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
case OP_PRUNE: {
|
|
222
|
+
std::string topic = r.str(r.u16());
|
|
223
|
+
if (r.ok && !topic.empty()) recv_prune(from, topic);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
case OP_IHAVE: {
|
|
227
|
+
std::string topic = r.str(r.u16());
|
|
228
|
+
const uint16_t count = r.u16();
|
|
229
|
+
std::vector<std::string> ids;
|
|
230
|
+
for (uint16_t i = 0; i < count && r.ok; ++i) ids.push_back(r.str(kIdSize));
|
|
231
|
+
if (r.ok && !topic.empty()) recv_ihave(from, topic, ids);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
case OP_IWANT: {
|
|
235
|
+
const uint16_t count = r.u16();
|
|
236
|
+
std::vector<std::string> ids;
|
|
237
|
+
for (uint16_t i = 0; i < count && r.ok; ++i) ids.push_back(r.str(kIdSize));
|
|
238
|
+
if (r.ok) recv_iwant(from, ids);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
case OP_PUBLISH: {
|
|
242
|
+
ByteView origin_bytes = r.bytes(PeerId::kSize);
|
|
243
|
+
const uint64_t seqno = r.u64();
|
|
244
|
+
const uint16_t topic_len = r.u16();
|
|
245
|
+
ByteView topic_bytes = r.bytes(topic_len);
|
|
246
|
+
if (!r.ok) return;
|
|
247
|
+
ByteView data = r.rest();
|
|
248
|
+
auto origin = PeerId::from_bytes(origin_bytes);
|
|
249
|
+
if (!origin) return;
|
|
250
|
+
std::string topic(reinterpret_cast<const char*>(topic_bytes.data()), topic_bytes.size());
|
|
251
|
+
recv_publish(from, payload, *origin, seqno, topic, data);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
default:
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
void PubSub::recv_subscription(const PeerId& from, const std::string& topic, bool subscribe) {
|
|
260
|
+
CtrlList grafts, prunes;
|
|
261
|
+
{
|
|
262
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
263
|
+
Topic& t = topics_[topic];
|
|
264
|
+
if (subscribe) {
|
|
265
|
+
t.subscribers.insert(from);
|
|
266
|
+
if (subscriptions_.count(topic)) maintain_mesh_locked(topic, grafts, prunes);
|
|
267
|
+
} else {
|
|
268
|
+
t.subscribers.erase(from);
|
|
269
|
+
t.mesh.erase(from);
|
|
270
|
+
t.fanout.erase(from);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
for (const auto& g : grafts) send_ctrl(g.first, OP_GRAFT, g.second);
|
|
274
|
+
for (const auto& p : prunes) send_ctrl(p.first, OP_PRUNE, p.second);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
void PubSub::recv_graft(const PeerId& from, const std::string& topic) {
|
|
278
|
+
bool reject = false;
|
|
279
|
+
{
|
|
280
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
281
|
+
Topic& t = topics_[topic];
|
|
282
|
+
if (subscriptions_.count(topic)) {
|
|
283
|
+
t.subscribers.insert(from); // a peer that grafts us is interested
|
|
284
|
+
t.mesh.insert(from);
|
|
285
|
+
} else {
|
|
286
|
+
reject = true; // we don't carry this topic — bounce them back out of the mesh
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (reject) send_ctrl(from, OP_PRUNE, topic);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
void PubSub::recv_prune(const PeerId& from, const std::string& topic) {
|
|
293
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
294
|
+
auto it = topics_.find(topic);
|
|
295
|
+
if (it != topics_.end()) it->second.mesh.erase(from);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
void PubSub::recv_publish(const PeerId& from, ByteView frame, const PeerId& origin, uint64_t seqno,
|
|
299
|
+
const std::string& topic, ByteView data) {
|
|
300
|
+
const std::string id = make_id(origin, seqno);
|
|
301
|
+
if (!mark_seen(id)) return; // already processed → stop the loop
|
|
302
|
+
|
|
303
|
+
switch (validate(origin, topic, data)) {
|
|
304
|
+
case ValidationResult::Accept: break;
|
|
305
|
+
case ValidationResult::Reject:
|
|
306
|
+
case ValidationResult::Ignore: return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
cache_message(id, topic, frame.to_bytes());
|
|
310
|
+
deliver_local(origin, topic, data);
|
|
311
|
+
|
|
312
|
+
// Forward along our mesh for the topic (only meaningful if we subscribe), never
|
|
313
|
+
// back to the peer we got it from.
|
|
314
|
+
std::vector<PeerId> forward;
|
|
315
|
+
{
|
|
316
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
317
|
+
if (subscriptions_.count(topic)) {
|
|
318
|
+
auto it = topics_.find(topic);
|
|
319
|
+
if (it != topics_.end())
|
|
320
|
+
for (const PeerId& p : it->second.mesh)
|
|
321
|
+
if (p != from) forward.push_back(p);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (const PeerId& p : forward) network_->send(p, MessageType::Gossip, frame);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
void PubSub::recv_ihave(const PeerId& from, const std::string& /*topic*/, const std::vector<std::string>& ids) {
|
|
328
|
+
std::vector<std::string> wanted;
|
|
329
|
+
{
|
|
330
|
+
std::lock_guard<std::mutex> lock(mcache_mutex_);
|
|
331
|
+
for (const std::string& id : ids)
|
|
332
|
+
if (id.size() == kIdSize && !seen_.count(id)) wanted.push_back(id);
|
|
333
|
+
}
|
|
334
|
+
if (!wanted.empty()) {
|
|
335
|
+
Bytes frame = build_iwant(wanted);
|
|
336
|
+
network_->send(from, MessageType::Gossip, ByteView(frame));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
void PubSub::recv_iwant(const PeerId& from, const std::vector<std::string>& ids) {
|
|
341
|
+
std::vector<Bytes> frames;
|
|
342
|
+
{
|
|
343
|
+
std::lock_guard<std::mutex> lock(mcache_mutex_);
|
|
344
|
+
for (const std::string& id : ids) {
|
|
345
|
+
auto it = mcache_.find(id);
|
|
346
|
+
if (it != mcache_.end()) frames.push_back(it->second.frame); // resend real content
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
for (const Bytes& f : frames) network_->send(from, MessageType::Gossip, ByteView(f));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── Peer lifecycle ────────────────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
void PubSub::on_new_peer(const Peer& peer) {
|
|
355
|
+
std::vector<std::string> topics;
|
|
356
|
+
{
|
|
357
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
358
|
+
topics.reserve(subscriptions_.size());
|
|
359
|
+
for (const auto& kv : subscriptions_) topics.push_back(kv.first);
|
|
360
|
+
}
|
|
361
|
+
// Announce our interests so the new peer can route to (and mesh with) us.
|
|
362
|
+
for (const std::string& topic : topics) send_ctrl(peer.id(), OP_SUBSCRIBE, topic);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
void PubSub::on_peer_gone(const PeerId& id) {
|
|
366
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
367
|
+
for (auto& kv : topics_) {
|
|
368
|
+
kv.second.subscribers.erase(id);
|
|
369
|
+
kv.second.mesh.erase(id);
|
|
370
|
+
kv.second.fanout.erase(id);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ── Heartbeat: mesh maintenance + gossip emission ─────────────────────────────
|
|
375
|
+
|
|
376
|
+
void PubSub::heartbeat_loop() {
|
|
377
|
+
while (running_.load()) {
|
|
378
|
+
do_heartbeat();
|
|
379
|
+
std::unique_lock<std::mutex> lock(hb_mutex_);
|
|
380
|
+
hb_cv_.wait_for(lock, config_.heartbeat_interval, [this] { return !running_.load(); });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
void PubSub::do_heartbeat() {
|
|
385
|
+
if (!network_) return;
|
|
386
|
+
|
|
387
|
+
// 1. Collect the ids worth gossiping, grouped by topic, from the recent windows.
|
|
388
|
+
std::unordered_map<std::string, std::vector<std::string>> gossip_by_topic;
|
|
389
|
+
{
|
|
390
|
+
std::lock_guard<std::mutex> lock(mcache_mutex_);
|
|
391
|
+
const int windows = std::min<int>(config_.history_gossip, static_cast<int>(history_.size()));
|
|
392
|
+
for (int i = 0; i < windows; ++i)
|
|
393
|
+
for (const std::string& id : history_[i]) {
|
|
394
|
+
auto it = mcache_.find(id);
|
|
395
|
+
if (it != mcache_.end()) gossip_by_topic[it->second.topic].push_back(id);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 2. Under the topic lock: maintain each subscribed mesh and pick IHAVE targets.
|
|
400
|
+
CtrlList grafts, prunes;
|
|
401
|
+
std::vector<std::pair<std::string, std::vector<PeerId>>> ihave_plan;
|
|
402
|
+
const auto now = std::chrono::steady_clock::now();
|
|
403
|
+
{
|
|
404
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
405
|
+
for (const auto& kv : subscriptions_) {
|
|
406
|
+
const std::string& topic = kv.first;
|
|
407
|
+
maintain_mesh_locked(topic, grafts, prunes);
|
|
408
|
+
Topic& t = topics_[topic];
|
|
409
|
+
std::vector<PeerId> cands;
|
|
410
|
+
for (const PeerId& p : t.subscribers)
|
|
411
|
+
if (!t.mesh.count(p)) cands.push_back(p);
|
|
412
|
+
ihave_plan.emplace_back(topic, random_sample(std::move(cands), config_.gossip_factor));
|
|
413
|
+
}
|
|
414
|
+
// Expire idle fanout sets for topics we no longer publish to.
|
|
415
|
+
for (auto& kv : topics_) {
|
|
416
|
+
Topic& t = kv.second;
|
|
417
|
+
if (!subscriptions_.count(kv.first) && !t.fanout.empty() &&
|
|
418
|
+
now - t.last_fanout >= config_.fanout_ttl)
|
|
419
|
+
t.fanout.clear();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// 3. Dispatch mesh control + per-topic IHAVE outside the lock.
|
|
424
|
+
for (const auto& g : grafts) send_ctrl(g.first, OP_GRAFT, g.second);
|
|
425
|
+
for (const auto& p : prunes) send_ctrl(p.first, OP_PRUNE, p.second);
|
|
426
|
+
for (const auto& plan : ihave_plan) {
|
|
427
|
+
auto git = gossip_by_topic.find(plan.first);
|
|
428
|
+
if (git == gossip_by_topic.end() || plan.second.empty()) continue;
|
|
429
|
+
Bytes frame = build_ihave(plan.first, git->second);
|
|
430
|
+
for (const PeerId& p : plan.second) network_->send(p, MessageType::Gossip, ByteView(frame));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// 4. Shift the gossip history forward and evict messages that aged out.
|
|
434
|
+
{
|
|
435
|
+
std::lock_guard<std::mutex> lock(mcache_mutex_);
|
|
436
|
+
history_.push_front({});
|
|
437
|
+
while (static_cast<int>(history_.size()) > config_.history_length) {
|
|
438
|
+
for (const std::string& id : history_.back()) mcache_.erase(id);
|
|
439
|
+
history_.pop_back();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
void PubSub::maintain_mesh_locked(const std::string& topic, CtrlList& grafts, CtrlList& prunes) {
|
|
445
|
+
if (!subscriptions_.count(topic)) return; // mesh only exists for topics we subscribe to
|
|
446
|
+
Topic& t = topics_[topic];
|
|
447
|
+
|
|
448
|
+
if (static_cast<int>(t.mesh.size()) < config_.mesh_low) {
|
|
449
|
+
// Below the low watermark: graft random non-mesh subscribers up to the target.
|
|
450
|
+
std::vector<PeerId> cands;
|
|
451
|
+
for (const PeerId& p : t.subscribers)
|
|
452
|
+
if (!t.mesh.count(p)) cands.push_back(p);
|
|
453
|
+
const int need = config_.mesh_target - static_cast<int>(t.mesh.size());
|
|
454
|
+
for (const PeerId& p : random_sample(std::move(cands), need)) {
|
|
455
|
+
t.mesh.insert(p);
|
|
456
|
+
grafts.emplace_back(p, topic);
|
|
457
|
+
}
|
|
458
|
+
} else if (static_cast<int>(t.mesh.size()) > config_.mesh_high) {
|
|
459
|
+
// Above the high watermark: prune random mesh peers back down to the target.
|
|
460
|
+
std::vector<PeerId> mesh_vec(t.mesh.begin(), t.mesh.end());
|
|
461
|
+
const int excess = static_cast<int>(t.mesh.size()) - config_.mesh_target;
|
|
462
|
+
for (const PeerId& p : random_sample(std::move(mesh_vec), excess)) {
|
|
463
|
+
t.mesh.erase(p);
|
|
464
|
+
prunes.emplace_back(p, topic);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
470
|
+
|
|
471
|
+
void PubSub::send_ctrl(const PeerId& to, uint8_t op, const std::string& topic) {
|
|
472
|
+
if (!network_) return;
|
|
473
|
+
Bytes frame = build_ctrl(op, topic);
|
|
474
|
+
network_->send(to, MessageType::Gossip, ByteView(frame));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
void PubSub::broadcast_ctrl(uint8_t op, const std::string& topic) {
|
|
478
|
+
if (!network_) return;
|
|
479
|
+
Bytes frame = build_ctrl(op, topic);
|
|
480
|
+
network_->broadcast(MessageType::Gossip, ByteView(frame));
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
void PubSub::deliver_local(const PeerId& from, const std::string& topic, ByteView data) {
|
|
484
|
+
Handler handler = handler_for(topic);
|
|
485
|
+
if (handler) handler(from, topic, data);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
PubSub::Handler PubSub::handler_for(const std::string& topic) const {
|
|
489
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
490
|
+
auto it = subscriptions_.find(topic);
|
|
491
|
+
return it == subscriptions_.end() ? Handler{} : it->second;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
ValidationResult PubSub::validate(const PeerId& from, const std::string& topic, ByteView data) {
|
|
495
|
+
Validator v;
|
|
496
|
+
{
|
|
497
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
498
|
+
auto it = validators_.find(topic);
|
|
499
|
+
if (it != validators_.end()) v = it->second;
|
|
500
|
+
else v = global_validator_;
|
|
501
|
+
}
|
|
502
|
+
return v ? v(from, topic, data) : ValidationResult::Accept;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
void PubSub::set_validator(const std::string& topic, Validator validator) {
|
|
506
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
507
|
+
if (topic.empty()) global_validator_ = std::move(validator);
|
|
508
|
+
else validators_[topic] = std::move(validator);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
bool PubSub::mark_seen(const std::string& id) {
|
|
512
|
+
std::lock_guard<std::mutex> lock(mcache_mutex_);
|
|
513
|
+
if (!seen_.insert(id).second) return false;
|
|
514
|
+
seen_order_.push_back(id);
|
|
515
|
+
if (seen_order_.size() > config_.seen_limit) {
|
|
516
|
+
seen_.erase(seen_order_.front());
|
|
517
|
+
seen_order_.pop_front();
|
|
518
|
+
}
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
void PubSub::cache_message(const std::string& id, const std::string& topic, const Bytes& frame) {
|
|
523
|
+
std::lock_guard<std::mutex> lock(mcache_mutex_);
|
|
524
|
+
if (history_.empty()) history_.push_front({});
|
|
525
|
+
auto inserted = mcache_.emplace(id, CachedMessage{topic, frame});
|
|
526
|
+
if (inserted.second) history_.front().push_back(id); // remember it in the current window
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
std::vector<PeerId> PubSub::random_sample(std::vector<PeerId> in, int n) {
|
|
530
|
+
if (n <= 0 || in.empty()) return {};
|
|
531
|
+
if (static_cast<int>(in.size()) <= n) return in;
|
|
532
|
+
std::lock_guard<std::mutex> lock(rng_mutex_);
|
|
533
|
+
std::shuffle(in.begin(), in.end(), rng_);
|
|
534
|
+
in.resize(n);
|
|
535
|
+
return in;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ── Read-only queries ─────────────────────────────────────────────────────────
|
|
539
|
+
|
|
540
|
+
bool PubSub::is_subscribed(const std::string& topic) const {
|
|
541
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
542
|
+
return subscriptions_.count(topic) > 0;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
std::vector<std::string> PubSub::subscribed_topics() const {
|
|
546
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
547
|
+
std::vector<std::string> out;
|
|
548
|
+
out.reserve(subscriptions_.size());
|
|
549
|
+
for (const auto& kv : subscriptions_) out.push_back(kv.first);
|
|
550
|
+
return out;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
std::vector<PeerId> PubSub::peers_for_topic(const std::string& topic) const {
|
|
554
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
555
|
+
auto it = topics_.find(topic);
|
|
556
|
+
if (it == topics_.end()) return {};
|
|
557
|
+
return std::vector<PeerId>(it->second.subscribers.begin(), it->second.subscribers.end());
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
std::vector<PeerId> PubSub::mesh_peers(const std::string& topic) const {
|
|
561
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
562
|
+
auto it = topics_.find(topic);
|
|
563
|
+
if (it == topics_.end()) return {};
|
|
564
|
+
return std::vector<PeerId>(it->second.mesh.begin(), it->second.mesh.end());
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
} // namespace librats
|