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,559 @@
1
+ #include "librats/dht/routing_table.h"
2
+ #include "librats/dht/bep42.h"
3
+ #include "librats/dht/log.h"
4
+
5
+ #include <algorithm>
6
+ #include <array>
7
+ #include <iomanip>
8
+ #include <sstream>
9
+ #include <string>
10
+
11
+ namespace librats {
12
+ namespace dht {
13
+
14
+ namespace {
15
+
16
+ // Locate a contact by id within one set (live or replacements).
17
+ std::vector<NodeEntry>::iterator find_by_id(std::vector<NodeEntry>& v, const NodeId& id) {
18
+ return std::find_if(v.begin(), v.end(), [&](const NodeEntry& n) { return n.id == id; });
19
+ }
20
+
21
+ // The worst contact in `v` (the one every other is "no worse than"), or end() if empty.
22
+ std::vector<NodeEntry>::iterator worst_of(std::vector<NodeEntry>& v) {
23
+ auto worst = v.begin();
24
+ for (auto it = v.begin(); it != v.end(); ++it)
25
+ if (it->is_worse_than(*worst)) worst = it;
26
+ return worst;
27
+ }
28
+
29
+ // log2 of a power-of-two bucket size: 8 -> 3 ... 128 -> 7.
30
+ constexpr int prefix_bits_for(int bucket_size) noexcept {
31
+ int bits = 0;
32
+ while ((1 << bits) < bucket_size) ++bits;
33
+ return bits;
34
+ }
35
+
36
+ // The largest live-set limit any bucket can have (the widest top bucket); see
37
+ // RoutingTable::bucket_limit. The spread classifier yields prefix_bits_for(limit)
38
+ // bits, so this caps the number of distinct sub-prefix slots.
39
+ constexpr int kMaxBucketLimit = static_cast<int>(kBucketSize) * 16;
40
+
41
+ // Classify a node by the few sub-prefix bits right after the bucket's shared prefix.
42
+ // For a non-last bucket we skip the one bit that *defines* the bucket (it's the same
43
+ // for every entry); the last bucket hasn't split yet and holds both sides, so we keep
44
+ // it. The result indexes a "spread slot" in [0, bucket_size).
45
+ uint8_t classify_prefix(int bucket_idx, bool last_bucket, int bucket_size, const NodeId& id) noexcept {
46
+ const int shift = bucket_idx + (last_bucket ? 0 : 1);
47
+ return static_cast<uint8_t>(bits_at(id, shift, prefix_bits_for(bucket_size)));
48
+ }
49
+
50
+ // True if every live node and `id` carry the same bit at position `idx`: a split at
51
+ // that bit wouldn't separate anyone, so it would be pointless.
52
+ bool all_on_same_side(const std::vector<NodeEntry>& live, const NodeId& id, int idx) noexcept {
53
+ const int byte = idx >> 3;
54
+ const int bit = idx & 7;
55
+ const uint8_t mask = static_cast<uint8_t>(0x80u >> bit);
56
+ int count[2] = {0, 0};
57
+ ++count[(id[byte] & mask) ? 1 : 0];
58
+ for (const auto& n : live) ++count[(n.id[byte] & mask) ? 1 : 0];
59
+ return count[0] == 0 || count[1] == 0;
60
+ }
61
+
62
+ } // namespace
63
+
64
+ RoutingTable::RoutingTable(const NodeId& self, bool extended)
65
+ : self_(self), extended_(extended) {
66
+ buckets_.emplace_back(); // always keep at least the catch-all bucket
67
+ }
68
+
69
+ int RoutingTable::bucket_index(const NodeId& id) const noexcept {
70
+ const int prefix = shared_prefix_bits(self_, id);
71
+ const int last = static_cast<int>(buckets_.size()) - 1;
72
+ return prefix < last ? prefix : last;
73
+ }
74
+
75
+ int RoutingTable::bucket_limit(int index) const noexcept {
76
+ if (!extended_) return static_cast<int>(kBucketSize);
77
+ // The top buckets hold more contacts (16k, 8k, 4k, 2k) — that's where the
78
+ // keyspace and the first lookup hop are densest.
79
+ static constexpr int kMult[4] = {16, 8, 4, 2};
80
+ return static_cast<int>(kBucketSize) * (index < 4 ? kMult[index] : 1);
81
+ }
82
+
83
+ void RoutingTable::heard_about(const NodeId& id, const Address& endpoint, bool verified) {
84
+ if (id == self_) return;
85
+ NodeEntry e(id, endpoint); // unpinged until it replies
86
+ e.verified = verified;
87
+ add_node(e);
88
+ }
89
+
90
+ bool RoutingTable::node_seen(const NodeId& id, const Address& endpoint,
91
+ uint16_t rtt, bool verified) {
92
+ if (id == self_) return false;
93
+ NodeEntry entry(id, endpoint);
94
+ entry.verified = verified;
95
+ entry.record_success(rtt); // confirmed (fail_count = 0), folds in the RTT
96
+ return add_node(entry);
97
+ }
98
+
99
+ bool RoutingTable::add_node(NodeEntry e) {
100
+ if (e.id == self_) return false;
101
+ const int idx = bucket_index(e.id);
102
+ Bucket& b = buckets_[idx];
103
+
104
+ // Already live → refresh in place. A confirmed sighting also records success;
105
+ // mere hearsay must not reset a live contact's quality. A confirmed contact that
106
+ // moved to a new endpoint re-points the IP index too.
107
+ if (auto it = find_by_id(b.live, e.id); it != b.live.end()) {
108
+ it->verified = it->verified || e.verified;
109
+ if (e.pinged()) {
110
+ if (it->endpoint.ip != e.endpoint.ip) { ip_untrack(it->endpoint.ip); ip_track(e.endpoint.ip); }
111
+ it->endpoint = e.endpoint;
112
+ it->record_success(e.rtt);
113
+ }
114
+ return true;
115
+ }
116
+
117
+ // Sitting in the replacement cache: a confirmed sighting pulls it out to promote,
118
+ // carrying its accumulated quality; mere hearsay leaves it parked. The erase
119
+ // untracks its IP; the shared insert below re-tracks it (at its current endpoint),
120
+ // so a promotion is IP-neutral even if the endpoint changed.
121
+ bool promoted = false;
122
+ if (auto it = find_by_id(b.replacements, e.id); it != b.replacements.end()) {
123
+ if (!e.pinged()) return false;
124
+ NodeEntry carried = *it;
125
+ carried.endpoint = e.endpoint;
126
+ carried.verified = carried.verified || e.verified;
127
+ carried.record_success(e.rtt);
128
+ ip_untrack(it->endpoint.ip);
129
+ b.replacements.erase(it);
130
+ e = carried;
131
+ promoted = true;
132
+ }
133
+
134
+ // A genuinely new IP must pass the IP-diversity gate (rule 1 + rule 2, public IPs
135
+ // only). A promoted contact is already one we know, so it skips the gate.
136
+ if (!promoted && !passes_ip_diversity(b, idx, e)) return false;
137
+
138
+ const int limit = bucket_limit(idx);
139
+ if (static_cast<int>(b.live.size()) < limit) {
140
+ b.live.push_back(e);
141
+ ip_track(e.endpoint.ip);
142
+ return true;
143
+ }
144
+
145
+ // The bucket is full. A confirmed contact may split it or displace a weaker one;
146
+ // hearsay just waits in the replacement cache.
147
+ if (e.confirmed()) {
148
+ if (can_split(idx, e)) {
149
+ split_bucket(); // buckets idx and idx + 1 now exist
150
+ const bool added = add_node(e); // re-home e against the now-deeper table
151
+ // The split freed live slots; pull the best standbys up into them, but
152
+ // only after e has claimed its rightful slot.
153
+ fill_from_replacements(idx);
154
+ fill_from_replacements(idx + 1);
155
+ return added;
156
+ }
157
+ if (replace_for_spread(b, idx, limit, e)) return true;
158
+ }
159
+ stash_replacement(b, e);
160
+ return false;
161
+ }
162
+
163
+ bool RoutingTable::can_split(int index, const NodeEntry& e) const {
164
+ return index + 1 == static_cast<int>(buckets_.size()) // only the last bucket splits
165
+ && static_cast<int>(buckets_.size()) < kBucketCount - 1 // bounded depth
166
+ && e.confirmed()
167
+ && (index == 0 || buckets_[index - 1].live.size() > 1) // don't deepen into near-empty
168
+ && !all_on_same_side(buckets_[index].live, e.id, index); // a split must actually separate
169
+ }
170
+
171
+ void RoutingTable::split_bucket() {
172
+ const int last = static_cast<int>(buckets_.size()) - 1;
173
+ buckets_.emplace_back(); // may reallocate buckets_ — hold no Bucket& across this
174
+
175
+ Bucket moved = std::move(buckets_[last]);
176
+ buckets_[last] = Bucket{};
177
+
178
+ // Contacts sharing exactly `last` prefix bits with us stay; closer ones (longer
179
+ // shared prefix) move to the new, deeper bucket.
180
+ const auto place = [&](std::vector<NodeEntry>& src, bool live) {
181
+ for (auto& n : src) {
182
+ const int dest = shared_prefix_bits(self_, n.id) <= last ? last : last + 1;
183
+ auto& bucket = buckets_[dest];
184
+ (live ? bucket.live : bucket.replacements).push_back(std::move(n));
185
+ }
186
+ };
187
+ place(moved.live, true);
188
+ place(moved.replacements, false);
189
+
190
+ // The deeper bucket has a smaller limit, so it may now overflow — spill the rest.
191
+ trim_to_limit(last);
192
+ trim_to_limit(last + 1);
193
+
194
+ // Structural milestone, not per-packet churn: a bucket deepens only a bounded number
195
+ // of times as the table grows, so this stays quiet (unlike add/seen/fail, which must
196
+ // never log). The live composition over time is the heartbeat's describe() job.
197
+ LOG_DEBUG("dht.route", "bucket split → " << buckets_.size() << " bucket(s): #" << last
198
+ << " keeps " << buckets_[last].live.size() << ", #" << (last + 1)
199
+ << " gets " << buckets_[last + 1].live.size() << " live");
200
+ }
201
+
202
+ bool RoutingTable::replace_for_spread(Bucket& b, int index, int limit, const NodeEntry& e) {
203
+ auto& live = b.live;
204
+ const bool last = (index + 1 == static_cast<int>(buckets_.size()));
205
+
206
+ // 1) A node that has actually failed a query is the first to go.
207
+ NodeEntry* stale = nullptr;
208
+ for (auto& n : live)
209
+ if (n.pinged() && n.fail_count > 0 && (!stale || n.fail_count > stale->fail_count))
210
+ stale = &n;
211
+ if (stale) { assign(*stale, e); return true; }
212
+
213
+ // 2) Otherwise keep a spread of sub-prefixes. Group the live nodes by their slot.
214
+ // classify_prefix yields prefix_bits_for(limit) bits, so the slot index is in
215
+ // [0, 2^prefix_bits). Size the table to the largest possible bucket limit so a
216
+ // wider top bucket can never index out of bounds (tied to bucket_limit's cap).
217
+ static constexpr int kMaxSlots = 1 << prefix_bits_for(kMaxBucketLimit);
218
+ static_assert(kMaxSlots <= 256, "slot index must fit in the uint8_t classify_prefix returns");
219
+ const uint8_t want = classify_prefix(index, last, limit, e.id);
220
+ std::array<std::vector<NodeEntry*>, kMaxSlots> slot{};
221
+ for (auto& n : live) slot[classify_prefix(index, last, limit, n.id)].push_back(&n);
222
+
223
+ const auto worst_in = [](const std::vector<NodeEntry*>& v) {
224
+ NodeEntry* worst = v.front();
225
+ for (auto* n : v) if (n->is_worse_than(*worst)) worst = n;
226
+ return worst;
227
+ };
228
+
229
+ if (!slot[want].empty()) {
230
+ // Our slot is taken: displace its worst node only if we're strictly better.
231
+ NodeEntry* worst = worst_in(slot[want]);
232
+ if (worst->is_worse_than(e)) { assign(*worst, e); return true; }
233
+ return false;
234
+ }
235
+
236
+ // Our slot is empty: make room by dropping the worst node from an over-full slot,
237
+ // so a healthy contact alone in its slot is never sacrificed.
238
+ NodeEntry* victim = nullptr;
239
+ for (const auto& s : slot)
240
+ if (s.size() > 1)
241
+ for (auto* n : s)
242
+ if (!victim || n->is_worse_than(*victim)) victim = n;
243
+ if (victim) { assign(*victim, e); return true; }
244
+ return false;
245
+ }
246
+
247
+ void RoutingTable::stash_replacement(Bucket& b, const NodeEntry& entry) {
248
+ auto& r = b.replacements;
249
+ if (auto it = find_by_id(r, entry.id); it != r.end()) {
250
+ assign(*it, entry);
251
+ return;
252
+ }
253
+ if (r.size() < kBucketSize) {
254
+ r.push_back(entry);
255
+ ip_track(entry.endpoint.ip);
256
+ return;
257
+ }
258
+ auto worst = worst_of(r);
259
+ if (worst != r.end() && worst->is_worse_than(entry)) assign(*worst, entry);
260
+ }
261
+
262
+ void RoutingTable::trim_to_limit(int index) {
263
+ Bucket& b = buckets_[index];
264
+ const int limit = bucket_limit(index);
265
+ // Spilling a live contact into replacements keeps it in the table, so the IP index
266
+ // is untouched. Only the final overflow — a contact dropped from the table — untracks.
267
+ while (static_cast<int>(b.live.size()) > limit) {
268
+ auto worst = worst_of(b.live);
269
+ b.replacements.push_back(std::move(*worst));
270
+ b.live.erase(worst);
271
+ }
272
+ while (b.replacements.size() > kBucketSize) {
273
+ auto worst = worst_of(b.replacements);
274
+ ip_untrack(worst->endpoint.ip);
275
+ b.replacements.erase(worst);
276
+ }
277
+ }
278
+
279
+ void RoutingTable::fill_from_replacements(int index) {
280
+ Bucket& b = buckets_[index];
281
+ const int limit = bucket_limit(index);
282
+ while (static_cast<int>(b.live.size()) < limit && !b.replacements.empty()) {
283
+ // Promote the best standby — the one no other standby is better than, so
284
+ // confirmed contacts go before unpinged hearsay.
285
+ auto best = b.replacements.begin();
286
+ for (auto it = b.replacements.begin(); it != b.replacements.end(); ++it)
287
+ if (best->is_worse_than(*it)) best = it;
288
+ b.live.push_back(std::move(*best));
289
+ b.replacements.erase(best);
290
+ }
291
+ }
292
+
293
+ void RoutingTable::node_failed(const NodeId& id, const Address& endpoint) {
294
+ const int idx = bucket_index(id);
295
+ Bucket& b = buckets_[idx];
296
+
297
+ auto it = find_by_id(b.live, id);
298
+ if (it == b.live.end()) {
299
+ // A standby we were probing can fail too — drop it if it's spent. Only when the
300
+ // endpoint matches, though: a different node claiming the same id must not knock
301
+ // out the standby we already have for it.
302
+ if (auto r = find_by_id(b.replacements, id);
303
+ r != b.replacements.end() && r->endpoint == endpoint) {
304
+ r->record_failure();
305
+ if (!r->pinged() || r->fail_count >= kMaxFailCount) {
306
+ ip_untrack(r->endpoint.ip);
307
+ b.replacements.erase(r);
308
+ }
309
+ }
310
+ return;
311
+ }
312
+
313
+ // The id is live but now points at a different endpoint — a more recent sighting from
314
+ // another node overwrote it. This timeout belongs to the old node, so ignore it
315
+ // rather than penalise (or evict) the current, healthy contact.
316
+ if (it->endpoint != endpoint) return;
317
+
318
+ it->record_failure();
319
+
320
+ // A liveness check already failed here, so if a standby is ready, evict the
321
+ // failed contact and let the best standbys take the freed slot(s).
322
+ if (!b.replacements.empty()) {
323
+ ip_untrack(it->endpoint.ip);
324
+ b.live.erase(it);
325
+ fill_from_replacements(idx);
326
+ prune_empty_back();
327
+ return;
328
+ }
329
+
330
+ // No standby: keep giving it chances until it's clearly dead.
331
+ if (!it->pinged() || it->fail_count >= kMaxFailCount) {
332
+ ip_untrack(it->endpoint.ip);
333
+ b.live.erase(it);
334
+ }
335
+ prune_empty_back();
336
+ }
337
+
338
+ std::vector<NodeEntry> RoutingTable::find_closest(const NodeId& target, std::size_t count,
339
+ bool include_unconfirmed) const {
340
+ std::vector<NodeEntry> result;
341
+ if (count == 0) return result;
342
+
343
+ const auto take = [&](const Bucket& b) {
344
+ for (const auto& n : b.live)
345
+ if (include_unconfirmed || n.confirmed()) result.push_back(n);
346
+ };
347
+
348
+ // By the XOR metric's prefix structure, the buckets form distance tiers around the
349
+ // target's own bucket `start`:
350
+ // - bucket `start` is the *closest* tier: every contact in it is strictly nearer
351
+ // than any contact in a deeper bucket, so if it alone yields `count` we're done.
352
+ // - the deeper buckets (> start) are all tied at the same leading distance bit and
353
+ // are unordered among themselves, so they must be taken as one whole tier or not
354
+ // at all — we can stop before them, never partway through.
355
+ // - the shallower buckets (< start) are each a strictly farther tier in order, so
356
+ // they can be walked closest-first with an early stop the moment we have enough.
357
+ const int start = bucket_index(target);
358
+ result.reserve(count + static_cast<std::size_t>(buckets_[start].live.size()));
359
+
360
+ take(buckets_[start]);
361
+ if (result.size() < count) // closest tier short -> the deeper tier is needed in full
362
+ for (int i = start + 1; i < static_cast<int>(buckets_.size()); ++i) take(buckets_[i]);
363
+ for (int i = start - 1; i >= 0 && result.size() < count; --i) take(buckets_[i]);
364
+
365
+ const std::size_t k = (std::min)(count, result.size());
366
+ std::partial_sort(result.begin(), result.begin() + k, result.end(),
367
+ [&](const NodeEntry& a, const NodeEntry& c) { return closer_to(a.id, c.id, target); });
368
+ if (result.size() > count) result.resize(count);
369
+ return result;
370
+ }
371
+
372
+ std::optional<NodeEntry> RoutingTable::next_to_refresh(std::chrono::steady_clock::time_point now) {
373
+ // Order by last touch: a never-contacted contact (last_seen == min) sorts first,
374
+ // then the least-recently-touched.
375
+ NodeEntry* best = nullptr;
376
+ for (auto& b : buckets_)
377
+ for (auto& n : b.live)
378
+ if (!best || n.last_seen < best->last_seen) best = &n;
379
+
380
+ if (!best) return std::nullopt;
381
+
382
+ // Stamp it as touched now: we're about to probe it, so the next refresh moves on to
383
+ // a different contact instead of re-probing this one while its probe is in flight.
384
+ // A reply refreshes last_seen again; a timeout drops the contact (node_failed).
385
+ best->last_seen = now;
386
+ return *best;
387
+ }
388
+
389
+ std::size_t RoutingTable::size() const {
390
+ std::size_t n = 0;
391
+ for (const auto& b : buckets_) n += b.live.size();
392
+ return n;
393
+ }
394
+
395
+ void RoutingTable::set_self(const NodeId& id) {
396
+ if (id == self_) return;
397
+
398
+ // Drain every contact, re-root the table to a single bucket, then re-home each
399
+ // one — bucket indices are all relative to self_ and are now stale.
400
+ std::vector<NodeEntry> all;
401
+ for (auto& b : buckets_) {
402
+ for (auto& n : b.live) all.push_back(std::move(n));
403
+ for (auto& n : b.replacements) all.push_back(std::move(n));
404
+ }
405
+
406
+ self_ = id;
407
+ buckets_.clear();
408
+ buckets_.emplace_back();
409
+ ip_count_.clear(); // rebuilt as add_node re-tracks each contact
410
+ for (const auto& n : all) add_node(n);
411
+ }
412
+
413
+ std::vector<NodeEntry> RoutingTable::good_contacts() const {
414
+ std::vector<NodeEntry> out;
415
+ for (const auto& b : buckets_)
416
+ for (const auto& n : b.live)
417
+ if (n.confirmed()) out.push_back(n);
418
+ return out;
419
+ }
420
+
421
+ void RoutingTable::load_contacts(const std::vector<NodeEntry>& contacts) {
422
+ for (const auto& n : contacts) add_node(n);
423
+ }
424
+
425
+ std::string RoutingTable::describe() const {
426
+ // One compact line per contact: "<id8> <ip:port> <rtt> <flags>", where flags are
427
+ // ? = only heard about (never replied) !N = N consecutive failures v = BEP 42-verified
428
+ const auto fmt_contact = [](const NodeEntry& n) {
429
+ std::ostringstream s;
430
+ s << short_hex(n.id) << ' ' << n.endpoint.to_string() << ' ';
431
+ if (n.rtt == NodeEntry::kRttUnknown) s << "-ms";
432
+ else s << n.rtt << "ms";
433
+ if (!n.pinged()) s << " ?";
434
+ else if (n.fail_count > 0) s << " !" << static_cast<int>(n.fail_count);
435
+ if (n.verified) s << " v";
436
+ return s.str();
437
+ };
438
+ // A 10-cell bar showing how full the live set is relative to this bucket's limit.
439
+ const auto fill_bar = [](std::size_t live, int limit) {
440
+ constexpr int width = 10;
441
+ int filled = limit > 0 ? static_cast<int>((live * width + limit / 2) / limit) : 0;
442
+ if (filled > width) filled = width;
443
+ return std::string(static_cast<std::size_t>(filled), '#') +
444
+ std::string(static_cast<std::size_t>(width - filled), '.');
445
+ };
446
+
447
+ std::ostringstream os;
448
+ os << "routing table: " << size() << " node(s) / " << buckets_.size()
449
+ << " bucket(s) self " << short_hex(self_);
450
+ for (int i = 0; i < static_cast<int>(buckets_.size()); ++i) {
451
+ const Bucket& b = buckets_[i];
452
+ const int limit = bucket_limit(i);
453
+
454
+ std::ostringstream cnt;
455
+ cnt << b.live.size() << '/' << limit;
456
+
457
+ os << "\n #" << std::right << std::setw(2) << i << " "
458
+ << std::left << std::setw(7) << cnt.str()
459
+ << "repl " << std::right << std::setw(2) << b.replacements.size() << " "
460
+ << '[' << fill_bar(b.live.size(), limit) << "] ";
461
+
462
+ if (b.live.empty()) { os << "(empty)"; continue; }
463
+
464
+ // best = the contact no other is better than; worst = the first to be evicted.
465
+ const NodeEntry* best = &b.live.front();
466
+ const NodeEntry* worst = &b.live.front();
467
+ for (const auto& n : b.live) {
468
+ if (best->is_worse_than(n)) best = &n;
469
+ if (n.is_worse_than(*worst)) worst = &n;
470
+ }
471
+ os << "best " << fmt_contact(*best);
472
+ if (b.live.size() > 1) os << " worst " << fmt_contact(*worst);
473
+ }
474
+ return os.str();
475
+ }
476
+
477
+ void RoutingTable::prune_empty_back() {
478
+ while (buckets_.size() > 1
479
+ && buckets_.back().live.empty()
480
+ && buckets_.back().replacements.empty())
481
+ buckets_.pop_back();
482
+ }
483
+
484
+ // -- IP-diversity admission ---------------------------------------------------
485
+
486
+ void RoutingTable::ip_track(const IpAddress& ip) {
487
+ if (is_public_address(ip)) ++ip_count_[ip];
488
+ }
489
+
490
+ void RoutingTable::ip_untrack(const IpAddress& ip) {
491
+ if (!is_public_address(ip)) return;
492
+ auto it = ip_count_.find(ip);
493
+ if (it == ip_count_.end()) return; // defensive: never underflow past zero
494
+ if (--it->second <= 0) ip_count_.erase(it);
495
+ }
496
+
497
+ void RoutingTable::assign(NodeEntry& slot, const NodeEntry& e) {
498
+ ip_untrack(slot.endpoint.ip); // the displaced contact leaves the table
499
+ slot = e;
500
+ ip_track(e.endpoint.ip); // ... and the new one takes its place
501
+ }
502
+
503
+ std::optional<RoutingTable::Located> RoutingTable::find_by_endpoint(const Address& ep) {
504
+ for (int i = 0; i < static_cast<int>(buckets_.size()); ++i) {
505
+ Bucket& b = buckets_[i];
506
+ for (std::size_t j = 0; j < b.live.size(); ++j)
507
+ if (b.live[j].endpoint == ep) return Located{i, true, j};
508
+ for (std::size_t j = 0; j < b.replacements.size(); ++j)
509
+ if (b.replacements[j].endpoint == ep) return Located{i, false, j};
510
+ }
511
+ return std::nullopt;
512
+ }
513
+
514
+ bool RoutingTable::passes_ip_diversity(Bucket& b, int idx, const NodeEntry& e) {
515
+ const IpAddress& ip = e.endpoint.ip;
516
+ if (!is_public_address(ip)) return true; // private/loopback/CGNAT: unconstrained
517
+
518
+ // Rule 1 — one contact per IP across the whole table. The same-id sighting was
519
+ // already handled in add_node, so a hit here means this IP is held under a
520
+ // *different* id: a routing-table poisoning signal.
521
+ if (ip_count_.find(ip) != ip_count_.end()) {
522
+ auto found = find_by_endpoint(e.endpoint);
523
+ if (!found) return false; // same IP, different port -> ignore newcomer
524
+ if (!e.pinged()) return false; // unconfirmed id swap -> ignore (poison)
525
+
526
+ // A *confirmed* contact is answering from an endpoint we already hold under
527
+ // another id. The old entry is now suspect, so drop it and don't trust the new
528
+ // one either — libtorrent's conservative "evict on changed id". The freed live
529
+ // slot refills from that bucket's standbys.
530
+ Bucket& eb = buckets_[found->bucket];
531
+ auto& set = found->live ? eb.live : eb.replacements;
532
+ ip_untrack(set[found->index].endpoint.ip);
533
+ set.erase(set.begin() + static_cast<std::ptrdiff_t>(found->index));
534
+ if (found->live) fill_from_replacements(found->bucket);
535
+ prune_empty_back();
536
+ return false;
537
+ }
538
+
539
+ // Rule 2 — at most one contact per /24 (v4) or /64 (v6) within this one bucket.
540
+ for (const auto& n : b.live)
541
+ if (ip_too_close(n.endpoint.ip, ip)) return false;
542
+ for (const auto& n : b.replacements)
543
+ if (ip_too_close(n.endpoint.ip, ip)) return false;
544
+ return true;
545
+ }
546
+
547
+ bool RoutingTable::ip_index_consistent() const {
548
+ std::unordered_map<IpAddress, int> expected;
549
+ for (const auto& b : buckets_) {
550
+ for (const auto& n : b.live)
551
+ if (is_public_address(n.endpoint.ip)) ++expected[n.endpoint.ip];
552
+ for (const auto& n : b.replacements)
553
+ if (is_public_address(n.endpoint.ip)) ++expected[n.endpoint.ip];
554
+ }
555
+ return expected == ip_count_;
556
+ }
557
+
558
+ } // namespace dht
559
+ } // namespace librats