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.
Files changed (319) hide show
  1. package/README.md +145 -331
  2. package/binding.gyp +16 -3
  3. package/lib/index.d.ts +288 -696
  4. package/lib/index.js +407 -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 +404 -179
  8. package/native-src/LICENSE +1 -1
  9. package/native-src/src/librats/bindings/rats.cpp +762 -0
  10. package/native-src/src/librats/bindings/rats.h +380 -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 +43 -0
  73. package/native-src/src/librats/core/types.h +103 -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/circuit_service.h +84 -0
  144. package/native-src/src/librats/node/config.h +110 -0
  145. package/native-src/src/librats/node/dial_service.h +54 -0
  146. package/native-src/src/librats/node/dialer.cpp +264 -0
  147. package/native-src/src/librats/node/dialer.h +188 -0
  148. package/native-src/src/librats/node/host_events.h +26 -0
  149. package/native-src/src/librats/node/identify.cpp +130 -0
  150. package/native-src/src/librats/node/identify.h +71 -0
  151. package/native-src/src/librats/node/nat_status.cpp +103 -0
  152. package/native-src/src/librats/node/nat_status.h +118 -0
  153. package/native-src/src/librats/node/node.cpp +865 -0
  154. package/native-src/src/librats/node/node.h +344 -0
  155. package/native-src/src/librats/node/node_context.h +33 -0
  156. package/native-src/src/librats/node/peer_network.h +91 -0
  157. package/native-src/src/librats/peer/peer.h +49 -0
  158. package/native-src/src/librats/peer/peer_book.cpp +181 -0
  159. package/native-src/src/librats/peer/peer_book.h +88 -0
  160. package/native-src/src/librats/peer/peer_id.cpp +72 -0
  161. package/native-src/src/librats/peer/peer_id.h +62 -0
  162. package/native-src/src/librats/peer/peer_info.h +37 -0
  163. package/native-src/src/librats/peer/peer_table.cpp +170 -0
  164. package/native-src/src/librats/peer/peer_table.h +148 -0
  165. package/native-src/src/librats/security/handshaker.h +66 -0
  166. package/native-src/src/librats/security/identity.h +43 -0
  167. package/native-src/src/librats/security/noise_security.cpp +122 -0
  168. package/native-src/src/librats/security/noise_security.h +37 -0
  169. package/native-src/src/librats/security/plaintext_security.h +106 -0
  170. package/native-src/src/librats/security/session.h +37 -0
  171. package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
  172. package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
  173. package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
  174. package/native-src/src/librats/subsystems/bittorrent.h +136 -0
  175. package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
  176. package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
  177. package/native-src/src/librats/subsystems/dht_service.h +36 -0
  178. package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
  179. package/native-src/src/librats/subsystems/file_transfer.h +367 -0
  180. package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
  181. package/native-src/src/librats/subsystems/hole_punch.h +290 -0
  182. package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
  183. package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
  184. package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
  185. package/native-src/src/librats/subsystems/message_json.cpp +112 -0
  186. package/native-src/src/librats/subsystems/message_json.h +88 -0
  187. package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
  188. package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
  189. package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
  190. package/native-src/src/librats/subsystems/ping_service.h +66 -0
  191. package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
  192. package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
  193. package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
  194. package/native-src/src/librats/subsystems/pubsub.h +175 -0
  195. package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
  196. package/native-src/src/librats/subsystems/reconnection.h +126 -0
  197. package/native-src/src/librats/subsystems/relay.cpp +1142 -0
  198. package/native-src/src/librats/subsystems/relay.h +211 -0
  199. package/native-src/src/librats/subsystems/relay_service.h +46 -0
  200. package/native-src/src/librats/transport/connection.cpp +343 -0
  201. package/native-src/src/librats/transport/connection.h +283 -0
  202. package/native-src/src/librats/transport/link.h +96 -0
  203. package/native-src/src/librats/transport/reactor.cpp +588 -0
  204. package/native-src/src/librats/transport/reactor.h +262 -0
  205. package/native-src/src/librats/transport/reactor_pool.h +81 -0
  206. package/native-src/src/librats/transport/relay_link.cpp +208 -0
  207. package/native-src/src/librats/transport/relay_link.h +303 -0
  208. package/native-src/src/librats/transport/tcp_link.cpp +49 -0
  209. package/native-src/src/librats/transport/tcp_link.h +43 -0
  210. package/native-src/src/librats/transport/udp_mux.cpp +617 -0
  211. package/native-src/src/librats/transport/udp_mux.h +363 -0
  212. package/native-src/src/librats/transport/udp_packet.cpp +121 -0
  213. package/native-src/src/librats/transport/udp_packet.h +190 -0
  214. package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
  215. package/native-src/src/librats/transport/udp_stream.h +614 -0
  216. package/native-src/src/librats/util/features.h.in +51 -0
  217. package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
  218. package/native-src/src/librats/util/fs.h +136 -0
  219. package/native-src/src/librats/util/json.cpp +1002 -0
  220. package/native-src/src/librats/util/json.h +444 -0
  221. package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
  222. package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
  223. package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
  224. package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
  225. package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
  226. package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
  227. package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
  228. package/native-src/src/librats/util/rats_export.h +69 -0
  229. package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
  230. package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
  231. package/native-src/src/librats/wire/frame.cpp +76 -0
  232. package/native-src/src/librats/wire/frame.h +111 -0
  233. package/native-src/src/librats/wire/message_router.cpp +45 -0
  234. package/native-src/src/librats/wire/message_router.h +47 -0
  235. package/package.json +5 -4
  236. package/scripts/build-librats.js +1 -0
  237. package/scripts/postinstall.js +3 -3
  238. package/scripts/prepare-package.js +4 -4
  239. package/scripts/verify-installation.js +63 -105
  240. package/src/librats_node.cpp +1067 -1323
  241. package/native-src/src/bencode.cpp +0 -485
  242. package/native-src/src/bencode.h +0 -145
  243. package/native-src/src/bittorrent.cpp +0 -14
  244. package/native-src/src/bittorrent.h +0 -74
  245. package/native-src/src/bt_bitfield.cpp +0 -372
  246. package/native-src/src/bt_bitfield.h +0 -316
  247. package/native-src/src/bt_choker.cpp +0 -228
  248. package/native-src/src/bt_choker.h +0 -147
  249. package/native-src/src/bt_client.cpp +0 -1047
  250. package/native-src/src/bt_client.h +0 -445
  251. package/native-src/src/bt_create_torrent.cpp +0 -677
  252. package/native-src/src/bt_create_torrent.h +0 -473
  253. package/native-src/src/bt_extension.cpp +0 -469
  254. package/native-src/src/bt_extension.h +0 -309
  255. package/native-src/src/bt_file_storage.cpp +0 -261
  256. package/native-src/src/bt_file_storage.h +0 -298
  257. package/native-src/src/bt_handshake.cpp +0 -134
  258. package/native-src/src/bt_handshake.h +0 -157
  259. package/native-src/src/bt_messages.cpp +0 -364
  260. package/native-src/src/bt_messages.h +0 -324
  261. package/native-src/src/bt_network.cpp +0 -1007
  262. package/native-src/src/bt_network.h +0 -417
  263. package/native-src/src/bt_peer_connection.cpp +0 -742
  264. package/native-src/src/bt_peer_connection.h +0 -592
  265. package/native-src/src/bt_piece_picker.cpp +0 -786
  266. package/native-src/src/bt_piece_picker.h +0 -473
  267. package/native-src/src/bt_resume_data.cpp +0 -410
  268. package/native-src/src/bt_resume_data.h +0 -249
  269. package/native-src/src/bt_torrent.cpp +0 -2120
  270. package/native-src/src/bt_torrent.h +0 -641
  271. package/native-src/src/bt_torrent_info.cpp +0 -659
  272. package/native-src/src/bt_torrent_info.h +0 -418
  273. package/native-src/src/bt_types.h +0 -621
  274. package/native-src/src/chained_send_buffer.cpp +0 -75
  275. package/native-src/src/chained_send_buffer.h +0 -137
  276. package/native-src/src/crypto/hkdf.c +0 -266
  277. package/native-src/src/crypto/poly1305.h +0 -36
  278. package/native-src/src/dht.cpp +0 -3311
  279. package/native-src/src/dht.h +0 -717
  280. package/native-src/src/disk_io.cpp +0 -632
  281. package/native-src/src/disk_io.h +0 -315
  282. package/native-src/src/file_transfer.cpp +0 -1415
  283. package/native-src/src/file_transfer.h +0 -286
  284. package/native-src/src/fs.h +0 -108
  285. package/native-src/src/gossipsub.cpp +0 -1139
  286. package/native-src/src/gossipsub.h +0 -403
  287. package/native-src/src/ice.cpp +0 -893
  288. package/native-src/src/ice.h +0 -559
  289. package/native-src/src/json.hpp +0 -25526
  290. package/native-src/src/librats.cpp +0 -2378
  291. package/native-src/src/librats.h +0 -2324
  292. package/native-src/src/librats_bittorrent.cpp +0 -601
  293. package/native-src/src/librats_c.cpp +0 -1557
  294. package/native-src/src/librats_c.h +0 -323
  295. package/native-src/src/librats_discovery.cpp +0 -402
  296. package/native-src/src/librats_encryption.cpp +0 -275
  297. package/native-src/src/librats_file_transfer.cpp +0 -144
  298. package/native-src/src/librats_gossipsub.cpp +0 -289
  299. package/native-src/src/librats_ice.cpp +0 -213
  300. package/native-src/src/librats_log_macros.h +0 -36
  301. package/native-src/src/librats_logging.cpp +0 -173
  302. package/native-src/src/librats_mdns.cpp +0 -166
  303. package/native-src/src/librats_persistence.cpp +0 -796
  304. package/native-src/src/librats_portmap.cpp +0 -419
  305. package/native-src/src/librats_reconnection.cpp +0 -218
  306. package/native-src/src/librats_statistic.cpp +0 -105
  307. package/native-src/src/librats_storage.cpp +0 -189
  308. package/native-src/src/rats_export.h +0 -17
  309. package/native-src/src/receive_buffer.cpp +0 -82
  310. package/native-src/src/receive_buffer.h +0 -127
  311. package/native-src/src/socket.h +0 -228
  312. package/native-src/src/threadmanager.cpp +0 -105
  313. package/native-src/src/threadmanager.h +0 -53
  314. package/native-src/src/tracker.cpp +0 -1264
  315. package/native-src/src/tracker.h +0 -319
  316. package/native-src/src/turn.cpp +0 -762
  317. package/native-src/src/turn.h +0 -460
  318. package/native-src/src/wakeup_pipe.h +0 -60
  319. /package/native-src/src/{os.h → librats/util/os.h} +0 -0
@@ -0,0 +1,181 @@
1
+ #include "librats/peer/peer_book.h"
2
+ #include "librats/util/fs.h"
3
+ #include "librats/util/json.h"
4
+
5
+ #include <algorithm>
6
+ #include <unordered_set>
7
+
8
+ namespace librats {
9
+
10
+ namespace {
11
+
12
+ // "best first" ordering: returns true if a ranks strictly before b.
13
+ bool better(const PeerRecord& a, const PeerRecord& b) {
14
+ const bool ca = a.last_connected > 0, cb = b.last_connected > 0;
15
+ if (ca != cb) return ca; // ever-connected first
16
+ if (a.last_connected != b.last_connected) return a.last_connected > b.last_connected;
17
+ if (a.connect_count != b.connect_count) return a.connect_count > b.connect_count;
18
+ if (a.fail_streak != b.fail_streak) return a.fail_streak < b.fail_streak;
19
+ return a.last_seen > b.last_seen;
20
+ }
21
+
22
+ bool is_stale(const PeerRecord& r, uint64_t now, uint64_t max_age_secs) {
23
+ return max_age_secs > 0 && r.last_seen > 0 && now > r.last_seen && (now - r.last_seen) > max_age_secs;
24
+ }
25
+
26
+ } // namespace
27
+
28
+ void PeerBook::load() {
29
+ const std::string text = read_file_text_cpp(path_);
30
+ if (text.empty()) return;
31
+
32
+ // Non-throwing parse: a malformed/legacy file simply yields no records
33
+ // rather than crashing startup.
34
+ const Json root = Json::parse(text, nullptr, /*allow_exceptions=*/false);
35
+ if (!root.is_array()) return;
36
+
37
+ std::lock_guard<std::mutex> lock(mutex_);
38
+ for (const auto& j : root) {
39
+ if (!j.is_object()) continue;
40
+ const std::string ip = j.value("ip", std::string());
41
+ const uint16_t port = static_cast<uint16_t>(j.value("port", 0));
42
+ const auto ipaddr = IpAddress::parse(ip);
43
+ if (!ipaddr || port == 0) continue; // need a valid numeric ip + port
44
+
45
+ PeerRecord r;
46
+ r.address = Address{*ipaddr, port};
47
+ // Remaining metadata fields are optional; absent ones keep their defaults.
48
+ const std::string idhex = j.value("id", std::string());
49
+ if (!idhex.empty()) { if (auto id = PeerId::from_hex(idhex)) r.id = *id; }
50
+ r.first_seen = j.value("first_seen", uint64_t{0});
51
+ r.last_seen = j.value("last_seen", uint64_t{0});
52
+ r.last_connected = j.value("last_connected", uint64_t{0});
53
+ r.connect_count = j.value("connect_count", uint32_t{0});
54
+ r.fail_streak = j.value("fail_streak", uint32_t{0});
55
+ records_[r.address] = r;
56
+ }
57
+ }
58
+
59
+ void PeerBook::save() const {
60
+ Json arr = Json::array();
61
+ {
62
+ std::lock_guard<std::mutex> lock(mutex_);
63
+ for (const auto& [key, r] : records_) {
64
+ Json j = Json::object();
65
+ j["ip"] = r.address.ip.to_string();
66
+ j["port"] = r.address.port;
67
+ if (!r.id.is_zero()) j["id"] = r.id.to_hex(); // omitted when never connected
68
+ j["first_seen"] = r.first_seen;
69
+ j["last_seen"] = r.last_seen;
70
+ j["last_connected"] = r.last_connected;
71
+ j["connect_count"] = r.connect_count;
72
+ j["fail_streak"] = r.fail_streak;
73
+ arr.push_back(std::move(j));
74
+ }
75
+ }
76
+ create_file(path_.c_str(), arr.dump(2).c_str());
77
+ }
78
+
79
+ void PeerBook::note_connected(const Address& address, const PeerId& id, uint64_t now) {
80
+ std::lock_guard<std::mutex> lock(mutex_);
81
+ PeerRecord& r = records_[address];
82
+ if (r.first_seen == 0) r.first_seen = now;
83
+ r.address = address;
84
+ if (!id.is_zero()) r.id = id;
85
+ r.last_seen = now;
86
+ r.last_connected = now;
87
+ r.connect_count++;
88
+ r.fail_streak = 0;
89
+ }
90
+
91
+ void PeerBook::note_failure(const Address& address, uint64_t /*now*/) {
92
+ std::lock_guard<std::mutex> lock(mutex_);
93
+ auto it = records_.find(address);
94
+ if (it == records_.end()) return; // only track failures for peers we already know
95
+ // Deliberately does NOT refresh last_seen: a peer that only ever fails must
96
+ // still age out (otherwise repeated give-ups would keep it forever "fresh").
97
+ it->second.fail_streak++;
98
+ }
99
+
100
+ void PeerBook::note_seen(const Address& address, uint64_t now) {
101
+ std::lock_guard<std::mutex> lock(mutex_);
102
+ PeerRecord& r = records_[address];
103
+ if (r.first_seen == 0) r.first_seen = now;
104
+ r.address = address;
105
+ r.last_seen = now;
106
+ }
107
+
108
+ bool PeerBook::remove(const Address& address) {
109
+ std::lock_guard<std::mutex> lock(mutex_);
110
+ return records_.erase(address) > 0;
111
+ }
112
+
113
+ std::vector<Address> PeerBook::best(size_t n, uint64_t now, uint64_t max_age_secs) const {
114
+ std::lock_guard<std::mutex> lock(mutex_);
115
+ std::vector<const PeerRecord*> recs;
116
+ recs.reserve(records_.size());
117
+ for (const auto& [key, r] : records_)
118
+ if (!is_stale(r, now, max_age_secs)) recs.push_back(&r);
119
+
120
+ std::sort(recs.begin(), recs.end(),
121
+ [](const PeerRecord* a, const PeerRecord* b) { return better(*a, *b); });
122
+
123
+ std::vector<Address> out;
124
+ for (const PeerRecord* r : recs) {
125
+ if (out.size() >= n) break;
126
+ out.push_back(r->address);
127
+ }
128
+ return out;
129
+ }
130
+
131
+ size_t PeerBook::prune(uint64_t now, uint64_t max_age_secs, size_t max_size) {
132
+ std::lock_guard<std::mutex> lock(mutex_);
133
+ const size_t before = records_.size();
134
+
135
+ if (max_age_secs > 0) {
136
+ for (auto it = records_.begin(); it != records_.end();) {
137
+ if (is_stale(it->second, now, max_age_secs)) it = records_.erase(it);
138
+ else ++it;
139
+ }
140
+ }
141
+
142
+ if (max_size > 0 && records_.size() > max_size) {
143
+ std::vector<const PeerRecord*> recs;
144
+ recs.reserve(records_.size());
145
+ for (const auto& [key, r] : records_) recs.push_back(&r);
146
+ std::sort(recs.begin(), recs.end(),
147
+ [](const PeerRecord* a, const PeerRecord* b) { return better(*a, *b); });
148
+ std::unordered_set<Address> keep;
149
+ keep.reserve(max_size);
150
+ for (size_t i = 0; i < max_size; ++i) keep.insert(recs[i]->address);
151
+ for (auto it = records_.begin(); it != records_.end();) {
152
+ if (keep.count(it->first)) ++it;
153
+ else it = records_.erase(it);
154
+ }
155
+ }
156
+
157
+ return before - records_.size();
158
+ }
159
+
160
+ std::vector<Address> PeerBook::all() const {
161
+ std::lock_guard<std::mutex> lock(mutex_);
162
+ std::vector<Address> out;
163
+ out.reserve(records_.size());
164
+ for (const auto& [key, r] : records_) out.push_back(r.address);
165
+ return out;
166
+ }
167
+
168
+ std::vector<PeerRecord> PeerBook::records() const {
169
+ std::lock_guard<std::mutex> lock(mutex_);
170
+ std::vector<PeerRecord> out;
171
+ out.reserve(records_.size());
172
+ for (const auto& [key, r] : records_) out.push_back(r);
173
+ return out;
174
+ }
175
+
176
+ size_t PeerBook::size() const {
177
+ std::lock_guard<std::mutex> lock(mutex_);
178
+ return records_.size();
179
+ }
180
+
181
+ } // namespace librats
@@ -0,0 +1,88 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file peer_book.h
5
+ * @brief Persistent address book of peers worth reconnecting to.
6
+ *
7
+ * Records every peer we have successfully connected to (and addresses we have
8
+ * learned), with light metadata: when we first/last saw it, when we last
9
+ * connected, how many times, and the current consecutive-failure streak. One
10
+ * ranked store serves BOTH needs at once — the "reconnect to recent peers"
11
+ * working set and the "remember everyone we ever met" archive. Callers query
12
+ * best() for the most promising addresses and rely on prune() to age out the
13
+ * long tail.
14
+ *
15
+ * Time is passed in (unix seconds) rather than read here, so the book stays a
16
+ * pure, testable data structure; the wall clock lives at the call site.
17
+ *
18
+ * Thread-safe. Backed by a human-readable JSON file: an array of objects, one
19
+ * per record, e.g.
20
+ * [
21
+ * {"ip":"127.0.0.1","port":4001,"id":"ab..","first_seen":1000,
22
+ * "last_seen":2000,"last_connected":2000,"connect_count":2,"fail_streak":0}
23
+ * ]
24
+ * The "id" field is omitted for peers we have only ever seen, never connected to.
25
+ */
26
+
27
+ #include "librats/util/rats_export.h"
28
+ #include "librats/core/address.h"
29
+ #include "librats/peer/peer_id.h"
30
+
31
+ #include <cstdint>
32
+ #include <mutex>
33
+ #include <string>
34
+ #include <unordered_map>
35
+ #include <vector>
36
+
37
+ namespace librats {
38
+
39
+ struct PeerRecord {
40
+ Address address;
41
+ PeerId id; ///< last known id (is_zero() if we never connected)
42
+ uint64_t first_seen = 0; ///< unix seconds first added/seen
43
+ uint64_t last_seen = 0; ///< unix seconds last seen (added / learned / connected)
44
+ uint64_t last_connected = 0; ///< unix seconds of last successful connection (0 = never)
45
+ uint32_t connect_count = 0; ///< successful connections
46
+ uint32_t fail_streak = 0; ///< consecutive failed dials since the last success
47
+ };
48
+
49
+ class RATS_API PeerBook {
50
+ public:
51
+ explicit PeerBook(std::string path) : path_(std::move(path)) {}
52
+
53
+ /// Load records from the backing file (no-op if it does not exist).
54
+ void load();
55
+ /// Persist the current records to the backing file.
56
+ void save() const;
57
+
58
+ /// A successful connection: refresh timestamps, bump connect_count, clear fails.
59
+ void note_connected(const Address& address, const PeerId& id, uint64_t now);
60
+ /// A failed dial to a known peer: bump its failure streak. Does NOT refresh
61
+ /// last_seen (so a peer that only ever fails still ages out). No-op if unknown.
62
+ void note_failure(const Address& address, uint64_t now);
63
+ /// Learned / manually added an address without (yet) connecting to it.
64
+ void note_seen(const Address& address, uint64_t now);
65
+
66
+ /// Remove an address entirely. Returns true if it was present.
67
+ bool remove(const Address& address);
68
+
69
+ /// Up to n most promising addresses (last seen within max_age_secs, or all if
70
+ /// max_age_secs == 0), best first: ever-connected before never-connected, then
71
+ /// most-recently-connected, then most connects, then fewest recent failures.
72
+ std::vector<Address> best(size_t n, uint64_t now, uint64_t max_age_secs) const;
73
+
74
+ /// Drop records last seen longer ago than max_age_secs (0 = no aging), then cap
75
+ /// to max_size (0 = no cap) keeping the best. Returns the number removed.
76
+ size_t prune(uint64_t now, uint64_t max_age_secs, size_t max_size);
77
+
78
+ std::vector<Address> all() const;
79
+ std::vector<PeerRecord> records() const;
80
+ size_t size() const;
81
+
82
+ private:
83
+ std::string path_;
84
+ mutable std::mutex mutex_;
85
+ std::unordered_map<Address, PeerRecord> records_; ///< keyed by the peer Address
86
+ };
87
+
88
+ } // namespace librats
@@ -0,0 +1,72 @@
1
+ #include "librats/peer/peer_id.h"
2
+
3
+ extern "C" {
4
+ #include "librats/crypto/sha256.h"
5
+ }
6
+
7
+ #include <cstring>
8
+
9
+ namespace librats {
10
+
11
+ namespace {
12
+ constexpr char kHexDigits[] = "0123456789abcdef";
13
+
14
+ int hex_value(char c) {
15
+ if (c >= '0' && c <= '9') return c - '0';
16
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
17
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
18
+ return -1;
19
+ }
20
+ } // namespace
21
+
22
+ PeerId PeerId::from_public_key(const uint8_t* key, size_t len) {
23
+ PeerId id;
24
+ rats_sha256_hash(id.bytes_.data(), key, len);
25
+ return id;
26
+ }
27
+
28
+ std::optional<PeerId> PeerId::from_bytes(ByteView raw) {
29
+ if (raw.size() != kSize) return std::nullopt;
30
+ PeerId id;
31
+ std::memcpy(id.bytes_.data(), raw.data(), kSize);
32
+ return id;
33
+ }
34
+
35
+ std::optional<PeerId> PeerId::from_hex(std::string_view hex) {
36
+ if (hex.size() != kSize * 2) return std::nullopt;
37
+ PeerId id;
38
+ for (size_t i = 0; i < kSize; ++i) {
39
+ const int hi = hex_value(hex[2 * i]);
40
+ const int lo = hex_value(hex[2 * i + 1]);
41
+ if (hi < 0 || lo < 0) return std::nullopt;
42
+ id.bytes_[i] = static_cast<uint8_t>((hi << 4) | lo);
43
+ }
44
+ return id;
45
+ }
46
+
47
+ std::string PeerId::to_hex() const {
48
+ std::string out(kSize * 2, '0');
49
+ for (size_t i = 0; i < kSize; ++i) {
50
+ out[2 * i] = kHexDigits[bytes_[i] >> 4];
51
+ out[2 * i + 1] = kHexDigits[bytes_[i] & 0x0F];
52
+ }
53
+ return out;
54
+ }
55
+
56
+ std::string PeerId::short_hex() const {
57
+ return to_hex().substr(0, 8);
58
+ }
59
+
60
+ bool PeerId::is_zero() const noexcept {
61
+ for (uint8_t b : bytes_) if (b != 0) return false;
62
+ return true;
63
+ }
64
+
65
+ size_t PeerId::Hash::operator()(const PeerId& id) const noexcept {
66
+ // The bytes are already a uniform hash; fold the first word into size_t.
67
+ size_t h = 0;
68
+ std::memcpy(&h, id.bytes_.data(), sizeof(h));
69
+ return h;
70
+ }
71
+
72
+ } // namespace librats
@@ -0,0 +1,62 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file peer_id.h
5
+ * @brief Self-certifying peer identity.
6
+ *
7
+ * A PeerId is the SHA-256 of a peer's static (Noise) public key. Because the
8
+ * Noise XX handshake proves possession of that key, a completed handshake also
9
+ * proves the remote's PeerId — identity is cryptographically bound to the key
10
+ * and cannot be forged (libp2p uses the same self-certifying scheme).
11
+ *
12
+ * This is a value type: cheap to copy, hashable, and ordered, so it slots
13
+ * straight into unordered_map / set as a key.
14
+ */
15
+
16
+ #include "librats/util/rats_export.h"
17
+ #include "librats/core/bytes.h"
18
+
19
+ #include <array>
20
+ #include <cstdint>
21
+ #include <optional>
22
+ #include <string>
23
+ #include <string_view>
24
+
25
+ namespace librats {
26
+
27
+ class RATS_API PeerId {
28
+ public:
29
+ static constexpr size_t kSize = 32; ///< SHA-256 digest length
30
+
31
+ PeerId() = default;
32
+
33
+ /// Derive the PeerId from a raw static public key (SHA-256 of the key).
34
+ static PeerId from_public_key(const uint8_t* key, size_t len);
35
+ static PeerId from_public_key(ByteView key) { return from_public_key(key.data(), key.size()); }
36
+
37
+ /// Wrap raw 32 id-bytes verbatim (NOT hashed). nullopt unless exactly kSize.
38
+ static std::optional<PeerId> from_bytes(ByteView raw);
39
+
40
+ /// Parse a 64-char lowercase/uppercase hex string. nullopt if malformed.
41
+ static std::optional<PeerId> from_hex(std::string_view hex);
42
+
43
+ std::string to_hex() const;
44
+ std::string short_hex() const; ///< first 8 hex chars, for logs
45
+
46
+ const std::array<uint8_t, kSize>& bytes() const noexcept { return bytes_; }
47
+ bool is_zero() const noexcept;
48
+
49
+ bool operator==(const PeerId& o) const noexcept { return bytes_ == o.bytes_; }
50
+ bool operator!=(const PeerId& o) const noexcept { return bytes_ != o.bytes_; }
51
+ bool operator<(const PeerId& o) const noexcept { return bytes_ < o.bytes_; }
52
+
53
+ /// Hash functor for use as an unordered_map/set key.
54
+ struct Hash {
55
+ size_t operator()(const PeerId& id) const noexcept;
56
+ };
57
+
58
+ private:
59
+ std::array<uint8_t, kSize> bytes_{};
60
+ };
61
+
62
+ } // namespace librats
@@ -0,0 +1,37 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file peer_info.h
5
+ * @brief Addressing/metadata for a peer — the shareable, persistable identity.
6
+ *
7
+ * PeerInfo is the "where / what" of a peer, kept separate from the live
8
+ * transport (Connection) and from the raw identity (PeerId). It is a value type:
9
+ * snapshotted into callbacks and into the PeerTable without touching live state.
10
+ */
11
+
12
+ #include "librats/core/types.h" // ConnRole
13
+ #include "librats/core/address.h"
14
+ #include "librats/peer/peer_id.h"
15
+
16
+ #include <vector>
17
+
18
+ namespace librats {
19
+
20
+ /// Transports a peer says it accepts, as a bitmask (see IdentifyMessage). Kept
21
+ /// separate from `transport`, which is the one this connection actually uses.
22
+ enum PeerTransports : uint8_t {
23
+ PeerTransportNone = 0,
24
+ PeerTransportTcp = 1 << 0,
25
+ PeerTransportUdp = 1 << 1,
26
+ };
27
+
28
+ struct PeerInfo {
29
+ PeerId id;
30
+ std::vector<Address> addresses; ///< known dialable addresses
31
+ ConnRole direction = ConnRole::Outbound;
32
+ TransportKind transport = TransportKind::Tcp; ///< wire this connection runs on
33
+ uint8_t supported_transports = PeerTransportNone; ///< what the peer advertised
34
+ std::string agent_version; ///< optional remote agent string
35
+ };
36
+
37
+ } // namespace librats
@@ -0,0 +1,170 @@
1
+ #include "librats/peer/peer_table.h"
2
+
3
+ #include <algorithm> // std::find
4
+ #include <mutex> // std::unique_lock (shared_lock comes from <shared_mutex>)
5
+
6
+ namespace librats {
7
+
8
+ namespace {
9
+
10
+ /// How much a transport is worth when two connections to the same peer have to be
11
+ /// ranked. Both ends see the same pair of transports and apply the same order, so
12
+ /// they converge on the same survivor without exchanging a word.
13
+ ///
14
+ /// Udp — one socket, one NAT mapping, a source port that can be dialed back
15
+ /// (see dialer.h). The best link there is.
16
+ /// Tcp — a direct link all the same, just a less useful one to a NAT.
17
+ /// Relay — not a direct link at all: every byte costs a third node bandwidth and
18
+ /// a round trip. Ranked last here for completeness; in practice a relayed
19
+ /// link is settled before this by the rule in add(), which lets ANY direct
20
+ /// link beat it regardless of role.
21
+ int transport_rank(TransportKind t) noexcept {
22
+ switch (t) {
23
+ case TransportKind::Udp: return 2;
24
+ case TransportKind::Tcp: return 1;
25
+ case TransportKind::Relay: return 0;
26
+ }
27
+ return 0;
28
+ }
29
+
30
+ } // namespace
31
+
32
+ PeerTable::AddOutcome PeerTable::add(const PeerInfo& info, PeerRoute route,
33
+ bool prefer_outbound) {
34
+ std::unique_lock<std::shared_mutex> lock(mutex_);
35
+ auto [it, inserted] = peers_.try_emplace(info.id, Entry{info, route});
36
+ if (inserted) {
37
+ count_.fetch_add(1, std::memory_order_relaxed);
38
+ return {AddResult::NewPeer, std::nullopt};
39
+ }
40
+
41
+ Entry& cur = it->second;
42
+ if (cur.route == route) { // same connection re-registering: refresh only
43
+ cur.info = info;
44
+ return {AddResult::Updated, std::nullopt};
45
+ }
46
+
47
+ // A different connection already serves this peer, and exactly one may survive.
48
+ // Both ends resolve the same pair, so every rule here must read something that
49
+ // looks identical from either side. Timing does not qualify — "whichever arrived
50
+ // first" gets a different answer at each end, and two ends that each keep the
51
+ // link the other just dropped are left with no link at all.
52
+ //
53
+ // one relayed, one not ⇒ not a race at all but an upgrade (or a late relay
54
+ // arriving under a direct link): the direct one wins,
55
+ // whatever the roles. Ranked FIRST, ahead of the role
56
+ // rule, because a relayed link that won on role would
57
+ // keep costing a third node bandwidth for the life of a
58
+ // peer we can reach ourselves. Both ends see the same
59
+ // pair of transports, so both reach this same verdict.
60
+ // opposite roles ⇒ cross-connect: keep the link started by the smaller id
61
+ // (prefer_outbound, opposite at each end by construction).
62
+ // different wires ⇒ a dial race whose attempts both got through. Both ends
63
+ // see the transports and rank them by the same explicit
64
+ // order (transport_rank above), so neither can rank the
65
+ // same pair differently.
66
+ // same role and wire ⇒ a reconnect, not a simultaneous pair: the old link is
67
+ // stale or already dead, so the newcomer is the live one.
68
+ const bool cur_relayed = cur.info.transport == TransportKind::Relay;
69
+ const bool new_relayed = info.transport == TransportKind::Relay;
70
+
71
+ bool keep_new = true;
72
+ if (cur_relayed != new_relayed) {
73
+ keep_new = !new_relayed;
74
+ } else if (cur.info.direction != info.direction) {
75
+ const ConnRole survivor = prefer_outbound ? ConnRole::Outbound : ConnRole::Inbound;
76
+ keep_new = (info.direction == survivor);
77
+ } else if (cur.info.transport != info.transport) {
78
+ keep_new = transport_rank(info.transport) > transport_rank(cur.info.transport);
79
+ }
80
+
81
+ if (keep_new) {
82
+ const PeerRoute loser = cur.route;
83
+ cur = Entry{info, route};
84
+ return {AddResult::Replaced, loser};
85
+ }
86
+ return {AddResult::Rejected, route}; // existing wins; caller closes the new connection
87
+ }
88
+
89
+ std::vector<Address> PeerTable::add_addresses(const PeerId& id, PeerRoute route,
90
+ const std::vector<Address>& addresses) {
91
+ std::vector<Address> added;
92
+ std::unique_lock<std::shared_mutex> lock(mutex_);
93
+ auto it = peers_.find(id);
94
+ if (it == peers_.end() || it->second.route != route) return added;
95
+
96
+ std::vector<Address>& known = it->second.info.addresses;
97
+ for (const Address& addr : addresses) {
98
+ if (known.size() >= kMaxAddressesPerPeer) break;
99
+ if (std::find(known.begin(), known.end(), addr) != known.end()) continue; // dedup
100
+ known.push_back(addr);
101
+ added.push_back(addr);
102
+ }
103
+ return added;
104
+ }
105
+
106
+ std::optional<PeerTable::Destination> PeerTable::destination(const PeerId& id) const {
107
+ std::shared_lock<std::shared_mutex> lock(mutex_);
108
+ auto it = peers_.find(id);
109
+ if (it == peers_.end()) return std::nullopt;
110
+ return Destination{it->second.route, it->second.writable, it->second.owed};
111
+ }
112
+
113
+ bool PeerTable::all_writable() const {
114
+ std::shared_lock<std::shared_mutex> lock(mutex_);
115
+ for (const auto& [id, entry] : peers_)
116
+ if (!entry.writable) return false;
117
+ return true;
118
+ }
119
+
120
+ void PeerTable::set_writable(const PeerId& id, PeerRoute route, bool writable) {
121
+ std::unique_lock<std::shared_mutex> lock(mutex_);
122
+ auto it = peers_.find(id);
123
+ if (it == peers_.end() || it->second.route != route) return;
124
+ it->second.writable = writable;
125
+ }
126
+
127
+ void PeerTable::set_supported_transports(const PeerId& id, PeerRoute route, uint8_t mask) {
128
+ std::unique_lock<std::shared_mutex> lock(mutex_);
129
+ auto it = peers_.find(id);
130
+ if (it == peers_.end() || it->second.route != route) return;
131
+ it->second.info.supported_transports = mask;
132
+ }
133
+
134
+ bool PeerTable::remove(const PeerId& id, PeerRoute route) {
135
+ std::unique_lock<std::shared_mutex> lock(mutex_);
136
+ auto it = peers_.find(id);
137
+ if (it == peers_.end() || it->second.route != route) return false;
138
+ peers_.erase(it);
139
+ count_.fetch_sub(1, std::memory_order_relaxed);
140
+ return true;
141
+ }
142
+
143
+ std::optional<PeerRoute> PeerTable::route(const PeerId& id) const {
144
+ std::shared_lock<std::shared_mutex> lock(mutex_);
145
+ auto it = peers_.find(id);
146
+ if (it == peers_.end()) return std::nullopt;
147
+ return it->second.route;
148
+ }
149
+
150
+ std::optional<PeerInfo> PeerTable::info(const PeerId& id) const {
151
+ std::shared_lock<std::shared_mutex> lock(mutex_);
152
+ auto it = peers_.find(id);
153
+ if (it == peers_.end()) return std::nullopt;
154
+ return it->second.info;
155
+ }
156
+
157
+ bool PeerTable::contains(const PeerId& id) const {
158
+ std::shared_lock<std::shared_mutex> lock(mutex_);
159
+ return peers_.find(id) != peers_.end();
160
+ }
161
+
162
+ std::vector<PeerInfo> PeerTable::snapshot() const {
163
+ std::shared_lock<std::shared_mutex> lock(mutex_);
164
+ std::vector<PeerInfo> out;
165
+ out.reserve(peers_.size());
166
+ for (const auto& [id, entry] : peers_) out.push_back(entry.info);
167
+ return out;
168
+ }
169
+
170
+ } // namespace librats