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,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