librats 1.0.2 → 2.1.4

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.
Files changed (313) hide show
  1. package/README.md +137 -332
  2. package/binding.gyp +16 -3
  3. package/lib/index.d.ts +271 -696
  4. package/lib/index.js +383 -44
  5. package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
  7. package/native-src/CMakeLists.txt +396 -179
  8. package/native-src/LICENSE +1 -1
  9. package/native-src/src/librats/bindings/rats.cpp +731 -0
  10. package/native-src/src/librats/bindings/rats.h +352 -0
  11. package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
  12. package/native-src/src/librats/bittorrent/bencode.h +176 -0
  13. package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
  14. package/native-src/src/librats/bittorrent/bitfield.h +76 -0
  15. package/native-src/src/librats/bittorrent/byte_io.h +58 -0
  16. package/native-src/src/librats/bittorrent/choker.cpp +25 -0
  17. package/native-src/src/librats/bittorrent/choker.h +46 -0
  18. package/native-src/src/librats/bittorrent/client.cpp +413 -0
  19. package/native-src/src/librats/bittorrent/client.h +227 -0
  20. package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
  21. package/native-src/src/librats/bittorrent/disk_io.h +150 -0
  22. package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
  23. package/native-src/src/librats/bittorrent/extensions.h +94 -0
  24. package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
  25. package/native-src/src/librats/bittorrent/file_storage.h +79 -0
  26. package/native-src/src/librats/bittorrent/log.h +42 -0
  27. package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
  28. package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
  29. package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
  30. package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
  31. package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
  32. package/native-src/src/librats/bittorrent/peer_list.h +75 -0
  33. package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
  34. package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
  35. package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
  36. package/native-src/src/librats/bittorrent/reactor.h +89 -0
  37. package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
  38. package/native-src/src/librats/bittorrent/resume_data.h +41 -0
  39. package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
  40. package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
  41. package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
  42. package/native-src/src/librats/bittorrent/torrent.h +260 -0
  43. package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
  44. package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
  45. package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
  46. package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
  47. package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
  48. package/native-src/src/librats/bittorrent/tracker.h +108 -0
  49. package/native-src/src/librats/bittorrent/types.cpp +206 -0
  50. package/native-src/src/librats/bittorrent/types.h +86 -0
  51. package/native-src/src/librats/core/address.cpp +35 -0
  52. package/native-src/src/librats/core/address.h +78 -0
  53. package/native-src/src/librats/core/bytes.h +69 -0
  54. package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
  55. package/native-src/src/librats/core/chained_send_buffer.h +183 -0
  56. package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
  57. package/native-src/src/librats/core/endpoint_parse.h +31 -0
  58. package/native-src/src/librats/core/event_bus.h +70 -0
  59. package/native-src/src/librats/core/host_endpoint.h +56 -0
  60. package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
  61. package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
  62. package/native-src/src/librats/core/ip_address.cpp +120 -0
  63. package/native-src/src/librats/core/ip_address.h +109 -0
  64. package/native-src/src/librats/core/mpsc_queue.h +47 -0
  65. package/native-src/src/librats/core/notifier.h +74 -0
  66. package/native-src/src/librats/core/receive_buffer.cpp +219 -0
  67. package/native-src/src/librats/core/receive_buffer.h +171 -0
  68. package/native-src/src/librats/core/service_registry.h +58 -0
  69. package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
  70. package/native-src/src/librats/core/socket.h +496 -0
  71. package/native-src/src/librats/core/timer_queue.h +105 -0
  72. package/native-src/src/librats/core/types.cpp +42 -0
  73. package/native-src/src/librats/core/types.h +93 -0
  74. package/native-src/src/librats/core/wakeup_pipe.h +83 -0
  75. package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
  76. package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
  77. package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
  78. package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
  79. package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
  80. package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
  81. package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
  82. package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
  83. package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
  84. package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
  85. package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
  86. package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
  87. package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
  88. package/native-src/src/librats/crypto/hkdf.c +266 -0
  89. package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
  90. package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
  91. package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
  92. package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
  93. package/native-src/src/librats/crypto/poly1305.h +37 -0
  94. package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
  95. package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
  96. package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
  97. package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
  98. package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
  99. package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
  100. package/native-src/src/librats/dht/announce.cpp +37 -0
  101. package/native-src/src/librats/dht/announce.h +41 -0
  102. package/native-src/src/librats/dht/bep42.cpp +109 -0
  103. package/native-src/src/librats/dht/bep42.h +48 -0
  104. package/native-src/src/librats/dht/dht.cpp +501 -0
  105. package/native-src/src/librats/dht/dht.h +119 -0
  106. package/native-src/src/librats/dht/dht_runner.cpp +103 -0
  107. package/native-src/src/librats/dht/dht_runner.h +71 -0
  108. package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
  109. package/native-src/src/librats/dht/dos_blocker.h +47 -0
  110. package/native-src/src/librats/dht/find_peers.cpp +52 -0
  111. package/native-src/src/librats/dht/find_peers.h +73 -0
  112. package/native-src/src/librats/dht/id.h +167 -0
  113. package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
  114. package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
  115. package/native-src/src/librats/dht/log.h +38 -0
  116. package/native-src/src/librats/dht/node.cpp +473 -0
  117. package/native-src/src/librats/dht/node.h +164 -0
  118. package/native-src/src/librats/dht/node_entry.h +81 -0
  119. package/native-src/src/librats/dht/observer.h +72 -0
  120. package/native-src/src/librats/dht/persistence.cpp +90 -0
  121. package/native-src/src/librats/dht/persistence.h +32 -0
  122. package/native-src/src/librats/dht/routing_table.cpp +559 -0
  123. package/native-src/src/librats/dht/routing_table.h +185 -0
  124. package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
  125. package/native-src/src/librats/dht/rpc_manager.h +77 -0
  126. package/native-src/src/librats/dht/storage.cpp +92 -0
  127. package/native-src/src/librats/dht/storage.h +74 -0
  128. package/native-src/src/librats/dht/transport.h +27 -0
  129. package/native-src/src/librats/dht/traversal.cpp +326 -0
  130. package/native-src/src/librats/dht/traversal.h +120 -0
  131. package/native-src/src/librats/dht/udp_transport.cpp +49 -0
  132. package/native-src/src/librats/dht/udp_transport.h +51 -0
  133. package/native-src/src/librats/mdns/log.h +22 -0
  134. package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
  135. package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
  136. package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
  137. package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
  138. package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
  139. package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
  140. package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
  141. package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
  142. package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
  143. package/native-src/src/librats/node/config.h +110 -0
  144. package/native-src/src/librats/node/dial_service.h +54 -0
  145. package/native-src/src/librats/node/dialer.cpp +264 -0
  146. package/native-src/src/librats/node/dialer.h +188 -0
  147. package/native-src/src/librats/node/host_events.h +26 -0
  148. package/native-src/src/librats/node/identify.cpp +130 -0
  149. package/native-src/src/librats/node/identify.h +71 -0
  150. package/native-src/src/librats/node/nat_status.cpp +103 -0
  151. package/native-src/src/librats/node/nat_status.h +118 -0
  152. package/native-src/src/librats/node/node.cpp +826 -0
  153. package/native-src/src/librats/node/node.h +333 -0
  154. package/native-src/src/librats/node/node_context.h +33 -0
  155. package/native-src/src/librats/node/peer_network.h +91 -0
  156. package/native-src/src/librats/peer/peer.h +49 -0
  157. package/native-src/src/librats/peer/peer_book.cpp +181 -0
  158. package/native-src/src/librats/peer/peer_book.h +88 -0
  159. package/native-src/src/librats/peer/peer_id.cpp +72 -0
  160. package/native-src/src/librats/peer/peer_id.h +62 -0
  161. package/native-src/src/librats/peer/peer_info.h +37 -0
  162. package/native-src/src/librats/peer/peer_table.cpp +137 -0
  163. package/native-src/src/librats/peer/peer_table.h +148 -0
  164. package/native-src/src/librats/security/handshaker.h +66 -0
  165. package/native-src/src/librats/security/identity.h +43 -0
  166. package/native-src/src/librats/security/noise_security.cpp +122 -0
  167. package/native-src/src/librats/security/noise_security.h +37 -0
  168. package/native-src/src/librats/security/plaintext_security.h +106 -0
  169. package/native-src/src/librats/security/session.h +37 -0
  170. package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
  171. package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
  172. package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
  173. package/native-src/src/librats/subsystems/bittorrent.h +136 -0
  174. package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
  175. package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
  176. package/native-src/src/librats/subsystems/dht_service.h +36 -0
  177. package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
  178. package/native-src/src/librats/subsystems/file_transfer.h +367 -0
  179. package/native-src/src/librats/subsystems/hole_punch.cpp +532 -0
  180. package/native-src/src/librats/subsystems/hole_punch.h +266 -0
  181. package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
  182. package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
  183. package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
  184. package/native-src/src/librats/subsystems/message_json.cpp +112 -0
  185. package/native-src/src/librats/subsystems/message_json.h +88 -0
  186. package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
  187. package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
  188. package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
  189. package/native-src/src/librats/subsystems/ping_service.h +66 -0
  190. package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
  191. package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
  192. package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
  193. package/native-src/src/librats/subsystems/pubsub.h +175 -0
  194. package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
  195. package/native-src/src/librats/subsystems/reconnection.h +126 -0
  196. package/native-src/src/librats/transport/connection.cpp +343 -0
  197. package/native-src/src/librats/transport/connection.h +283 -0
  198. package/native-src/src/librats/transport/link.h +96 -0
  199. package/native-src/src/librats/transport/reactor.cpp +540 -0
  200. package/native-src/src/librats/transport/reactor.h +224 -0
  201. package/native-src/src/librats/transport/reactor_pool.h +81 -0
  202. package/native-src/src/librats/transport/tcp_link.cpp +49 -0
  203. package/native-src/src/librats/transport/tcp_link.h +43 -0
  204. package/native-src/src/librats/transport/udp_mux.cpp +617 -0
  205. package/native-src/src/librats/transport/udp_mux.h +363 -0
  206. package/native-src/src/librats/transport/udp_packet.cpp +121 -0
  207. package/native-src/src/librats/transport/udp_packet.h +190 -0
  208. package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
  209. package/native-src/src/librats/transport/udp_stream.h +614 -0
  210. package/native-src/src/librats/util/features.h.in +51 -0
  211. package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
  212. package/native-src/src/librats/util/fs.h +136 -0
  213. package/native-src/src/librats/util/json.cpp +1002 -0
  214. package/native-src/src/librats/util/json.h +444 -0
  215. package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
  216. package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
  217. package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
  218. package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
  219. package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
  220. package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
  221. package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
  222. package/native-src/src/librats/util/rats_export.h +69 -0
  223. package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
  224. package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
  225. package/native-src/src/librats/wire/frame.cpp +76 -0
  226. package/native-src/src/librats/wire/frame.h +110 -0
  227. package/native-src/src/librats/wire/message_router.cpp +45 -0
  228. package/native-src/src/librats/wire/message_router.h +47 -0
  229. package/package.json +5 -4
  230. package/scripts/build-librats.js +1 -0
  231. package/scripts/postinstall.js +3 -3
  232. package/scripts/prepare-package.js +4 -4
  233. package/scripts/verify-installation.js +63 -105
  234. package/src/librats_node.cpp +1042 -1324
  235. package/native-src/src/bencode.cpp +0 -485
  236. package/native-src/src/bencode.h +0 -145
  237. package/native-src/src/bittorrent.cpp +0 -14
  238. package/native-src/src/bittorrent.h +0 -74
  239. package/native-src/src/bt_bitfield.cpp +0 -372
  240. package/native-src/src/bt_bitfield.h +0 -316
  241. package/native-src/src/bt_choker.cpp +0 -228
  242. package/native-src/src/bt_choker.h +0 -147
  243. package/native-src/src/bt_client.cpp +0 -1047
  244. package/native-src/src/bt_client.h +0 -445
  245. package/native-src/src/bt_create_torrent.cpp +0 -677
  246. package/native-src/src/bt_create_torrent.h +0 -473
  247. package/native-src/src/bt_extension.cpp +0 -469
  248. package/native-src/src/bt_extension.h +0 -309
  249. package/native-src/src/bt_file_storage.cpp +0 -261
  250. package/native-src/src/bt_file_storage.h +0 -298
  251. package/native-src/src/bt_handshake.cpp +0 -134
  252. package/native-src/src/bt_handshake.h +0 -157
  253. package/native-src/src/bt_messages.cpp +0 -364
  254. package/native-src/src/bt_messages.h +0 -324
  255. package/native-src/src/bt_network.cpp +0 -1007
  256. package/native-src/src/bt_network.h +0 -417
  257. package/native-src/src/bt_peer_connection.cpp +0 -742
  258. package/native-src/src/bt_peer_connection.h +0 -592
  259. package/native-src/src/bt_piece_picker.cpp +0 -786
  260. package/native-src/src/bt_piece_picker.h +0 -473
  261. package/native-src/src/bt_resume_data.cpp +0 -410
  262. package/native-src/src/bt_resume_data.h +0 -249
  263. package/native-src/src/bt_torrent.cpp +0 -2120
  264. package/native-src/src/bt_torrent.h +0 -641
  265. package/native-src/src/bt_torrent_info.cpp +0 -659
  266. package/native-src/src/bt_torrent_info.h +0 -418
  267. package/native-src/src/bt_types.h +0 -621
  268. package/native-src/src/chained_send_buffer.cpp +0 -75
  269. package/native-src/src/chained_send_buffer.h +0 -137
  270. package/native-src/src/crypto/hkdf.c +0 -266
  271. package/native-src/src/crypto/poly1305.h +0 -36
  272. package/native-src/src/dht.cpp +0 -3311
  273. package/native-src/src/dht.h +0 -717
  274. package/native-src/src/disk_io.cpp +0 -632
  275. package/native-src/src/disk_io.h +0 -315
  276. package/native-src/src/file_transfer.cpp +0 -1415
  277. package/native-src/src/file_transfer.h +0 -286
  278. package/native-src/src/fs.h +0 -108
  279. package/native-src/src/gossipsub.cpp +0 -1139
  280. package/native-src/src/gossipsub.h +0 -403
  281. package/native-src/src/ice.cpp +0 -893
  282. package/native-src/src/ice.h +0 -559
  283. package/native-src/src/json.hpp +0 -25526
  284. package/native-src/src/librats.cpp +0 -2378
  285. package/native-src/src/librats.h +0 -2324
  286. package/native-src/src/librats_bittorrent.cpp +0 -601
  287. package/native-src/src/librats_c.cpp +0 -1557
  288. package/native-src/src/librats_c.h +0 -323
  289. package/native-src/src/librats_discovery.cpp +0 -402
  290. package/native-src/src/librats_encryption.cpp +0 -275
  291. package/native-src/src/librats_file_transfer.cpp +0 -144
  292. package/native-src/src/librats_gossipsub.cpp +0 -289
  293. package/native-src/src/librats_ice.cpp +0 -213
  294. package/native-src/src/librats_log_macros.h +0 -36
  295. package/native-src/src/librats_logging.cpp +0 -173
  296. package/native-src/src/librats_mdns.cpp +0 -166
  297. package/native-src/src/librats_persistence.cpp +0 -796
  298. package/native-src/src/librats_portmap.cpp +0 -419
  299. package/native-src/src/librats_reconnection.cpp +0 -218
  300. package/native-src/src/librats_statistic.cpp +0 -105
  301. package/native-src/src/librats_storage.cpp +0 -189
  302. package/native-src/src/rats_export.h +0 -17
  303. package/native-src/src/receive_buffer.cpp +0 -82
  304. package/native-src/src/receive_buffer.h +0 -127
  305. package/native-src/src/socket.h +0 -228
  306. package/native-src/src/threadmanager.cpp +0 -105
  307. package/native-src/src/threadmanager.h +0 -53
  308. package/native-src/src/tracker.cpp +0 -1264
  309. package/native-src/src/tracker.h +0 -319
  310. package/native-src/src/turn.cpp +0 -762
  311. package/native-src/src/turn.h +0 -460
  312. package/native-src/src/wakeup_pipe.h +0 -60
  313. /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