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,605 @@
1
+ #include "librats/subsystems/hole_punch.h"
2
+ #include "librats/node/dial_service.h"
3
+ #include "librats/node/node_context.h"
4
+ #include "librats/peer/peer_info.h"
5
+ #include "librats/util/logger.h"
6
+
7
+ #include <algorithm>
8
+ #include <optional>
9
+
10
+ namespace librats {
11
+
12
+ namespace {
13
+
14
+ constexpr uint8_t kVersion = 1;
15
+
16
+ // Envelope ops — what a message is *for the relay*.
17
+ constexpr uint8_t kOpRelay = 0; ///< sender → relay: pass this on to dst
18
+ constexpr uint8_t kOpRelayed = 1; ///< relay → destination: this came from src
19
+
20
+ // Inner kinds — what the two punching peers say to each other.
21
+ constexpr uint8_t kKindConnect = 0;
22
+ constexpr uint8_t kKindSync = 1;
23
+
24
+ /// Ceiling on addresses a Connect may carry, independent of the local config: a
25
+ /// peer must not be able to make us walk a longer list than we would ever send.
26
+ constexpr size_t kMaxAddressesOnWire = 8;
27
+
28
+ /// Largest envelope worth parsing. Version + op + id + kind + count + the address
29
+ /// list at its widest (IPv6). Anything past this is malformed by construction.
30
+ constexpr size_t kMaxMessageSize = 2 + PeerId::kSize + 3 + kMaxAddressesOnWire * 19;
31
+
32
+ /// The `role` byte of a Connect: is this opening a rendezvous, or answering one?
33
+ constexpr uint8_t kRoleOpening = 0;
34
+ constexpr uint8_t kRoleAnswer = 1;
35
+
36
+ void put_u16(Bytes& out, uint16_t v) {
37
+ out.push_back(static_cast<uint8_t>(v >> 8));
38
+ out.push_back(static_cast<uint8_t>(v & 0xFF));
39
+ }
40
+
41
+ uint16_t get_u16(const uint8_t* p) {
42
+ return static_cast<uint16_t>((static_cast<uint16_t>(p[0]) << 8) | p[1]);
43
+ }
44
+
45
+ void put_address(Bytes& out, const Address& a) {
46
+ const ByteView ip = a.ip.bytes(); // 4 (v4) or 16 (v6) raw bytes
47
+ out.push_back(static_cast<uint8_t>(ip.size()));
48
+ out.insert(out.end(), ip.begin(), ip.end());
49
+ put_u16(out, a.port);
50
+ }
51
+
52
+ /// Bounds-checked forward reader: any short read aborts the whole message, which
53
+ /// is the only way a hostile payload is allowed to affect us.
54
+ struct Reader {
55
+ const uint8_t* p;
56
+ size_t n;
57
+
58
+ const uint8_t* take(size_t k) {
59
+ if (n < k) return nullptr;
60
+ const uint8_t* at = p;
61
+ p += k; n -= k;
62
+ return at;
63
+ }
64
+ };
65
+
66
+ Bytes encode_connect(bool opening, const std::vector<Address>& addresses) {
67
+ Bytes out;
68
+ out.push_back(kKindConnect);
69
+ out.push_back(opening ? kRoleOpening : kRoleAnswer);
70
+ const size_t count = (std::min)(addresses.size(), kMaxAddressesOnWire);
71
+ out.push_back(static_cast<uint8_t>(count));
72
+ for (size_t i = 0; i < count; ++i) put_address(out, addresses[i]);
73
+ return out;
74
+ }
75
+
76
+ struct ConnectBody {
77
+ bool opening = false;
78
+ std::vector<Address> addresses;
79
+ };
80
+
81
+ /// Parse a Connect body (everything after the kind byte). nullopt on anything
82
+ /// malformed — a partially-read list is never acted on.
83
+ std::optional<ConnectBody> decode_connect(ByteView body) {
84
+ Reader r{body.data(), body.size()};
85
+ const uint8_t* role_p = r.take(1);
86
+ if (!role_p) return std::nullopt;
87
+ if (*role_p != kRoleOpening && *role_p != kRoleAnswer) return std::nullopt;
88
+
89
+ const uint8_t* count_p = r.take(1);
90
+ if (!count_p) return std::nullopt;
91
+ const uint8_t count = *count_p;
92
+ if (count > kMaxAddressesOnWire) return std::nullopt;
93
+
94
+ std::vector<Address> out;
95
+ out.reserve(count);
96
+ for (uint8_t i = 0; i < count; ++i) {
97
+ const uint8_t* len_p = r.take(1);
98
+ if (!len_p) return std::nullopt;
99
+ const uint8_t ip_len = *len_p;
100
+ if (ip_len != 4 && ip_len != 16) return std::nullopt;
101
+ const uint8_t* ip_p = r.take(ip_len);
102
+ if (!ip_p) return std::nullopt;
103
+ const uint8_t* port_p = r.take(2);
104
+ if (!port_p) return std::nullopt;
105
+
106
+ const auto ip = IpAddress::from_bytes(ByteView(ip_p, ip_len));
107
+ if (!ip) return std::nullopt;
108
+ Address addr{*ip, get_u16(port_p)};
109
+ if (!addr.is_valid() || addr.ip.is_any()) continue; // not dialable; skip, don't fail
110
+ out.push_back(addr);
111
+ }
112
+ return ConnectBody{*role_p == kRoleOpening, std::move(out)};
113
+ }
114
+
115
+ Bytes envelope(uint8_t op, const PeerId& id, const Bytes& inner) {
116
+ Bytes out;
117
+ out.reserve(2 + PeerId::kSize + inner.size());
118
+ out.push_back(kVersion);
119
+ out.push_back(op);
120
+ const auto& bytes = id.bytes();
121
+ out.insert(out.end(), bytes.begin(), bytes.end());
122
+ out.insert(out.end(), inner.begin(), inner.end());
123
+ return out;
124
+ }
125
+
126
+ } // namespace
127
+
128
+ HolePunch::HolePunch() : HolePunch(Config()) {}
129
+ HolePunch::HolePunch(Config config) : config_(std::move(config)) {}
130
+ HolePunch::~HolePunch() { stop(); }
131
+
132
+ void HolePunch::attach(NodeContext& ctx) {
133
+ network_ = &ctx.network;
134
+ // Both are optional by construction: resolve() answers nullptr when nothing
135
+ // provides the interface, and a node that cannot dial a specific endpoint or
136
+ // does not know its own external one simply cannot punch — it can still relay.
137
+ dialer_ = ctx.services.get<DialService>();
138
+ external_ = ctx.services.get<ExternalAddressService>();
139
+ services_ = &ctx.services;
140
+
141
+ // Offer punching to sibling modules. Registered even without a dialer: punch()
142
+ // answers false on its own then, which is exactly what a caller expects from a
143
+ // node that can only relay.
144
+ ctx.services.provide<HolePunchService>(this);
145
+
146
+ network_->on(MessageType::Punch,
147
+ [this](const Peer& peer, ByteView payload) { handle(peer, payload); });
148
+
149
+ // A peer arriving retires its session at once, rather than at the next tick.
150
+ // Watching for the peer — not for our dial reporting success — is the reliable
151
+ // signal: a punch lands in both directions and the one that survives is often
152
+ // the INBOUND link, which no dial of ours would ever report. The worker still
153
+ // reconciles sessions against the peer set, so this is the fast path, not the
154
+ // only one.
155
+ network_->on_peer_connected([this](const Peer& peer) {
156
+ // A relayed link is not what a punch was for — it is the fallback the punch
157
+ // is trying to make unnecessary, and a punch may well have been started
158
+ // BECAUSE it came up (see subsystems/relay.h). Retiring the session here
159
+ // would call the upgrade off the moment it began.
160
+ const auto info = peer.info();
161
+ if (info && info->transport == TransportKind::Relay) return;
162
+ std::lock_guard<std::mutex> lock(mutex_);
163
+ sessions_.erase(peer.id());
164
+ });
165
+ }
166
+
167
+ void HolePunch::start() {
168
+ // Resolved here rather than in attach(): Relay may be attached after us, and
169
+ // every attach() runs before any start(). Stored before running_ goes true, so
170
+ // an escalation on a reactor thread never sees a half-initialised state.
171
+ if (config_.relay_on_failure && services_)
172
+ relay_.store(services_->get<RelayService>());
173
+
174
+ if (running_.exchange(true)) return;
175
+ worker_ = std::thread([this] { loop(); });
176
+ }
177
+
178
+ void HolePunch::stop() {
179
+ relay_.store(nullptr);
180
+ if (!running_.exchange(false)) return;
181
+ cv_.notify_all();
182
+ if (worker_.joinable()) worker_.join();
183
+
184
+ std::lock_guard<std::mutex> lock(mutex_);
185
+ sessions_.clear();
186
+ cooldown_.clear();
187
+ }
188
+
189
+ // ── Starting a punch ────────────────────────────────────────────────────────
190
+
191
+ bool HolePunch::punch(const PeerId& target) {
192
+ if (!running_.load() || !network_ || !dialer_) return false;
193
+ if (target == network_->local_id()) return false;
194
+
195
+ // Already reachable the ordinary way. Checked before any state is created so a
196
+ // caller can punch() freely on discovery without having to check first. A
197
+ // RELAYED peer is deliberately not "already reachable": punching it is how the
198
+ // circuit gets replaced by a direct link (see subsystems/relay.h).
199
+ for (const PeerId& id : directly_connected())
200
+ if (id == target) return false;
201
+
202
+ if (config_.skip_when_endpoint_dependent && external_ &&
203
+ external_->udp_mapping() == NatMapping::EndpointDependent) {
204
+ LOG_DEBUG("punch", "Not punching to " << target.short_hex()
205
+ << ": our own mapping is per-destination (symmetric NAT)");
206
+ // Nothing about this will improve with time or retries: no endpoint we can
207
+ // advertise is the one the target's packets would arrive on. This is
208
+ // exactly the case a relayed path exists for.
209
+ escalate_to_relay(target);
210
+ return false;
211
+ }
212
+
213
+ const auto addresses = own_punch_addresses();
214
+ if (addresses.empty()) {
215
+ LOG_DEBUG("punch", "Not punching to " << target.short_hex()
216
+ << ": no external datagram endpoint to advertise yet");
217
+ escalate_to_relay(target);
218
+ return false;
219
+ }
220
+
221
+ {
222
+ std::lock_guard<std::mutex> lock(mutex_);
223
+ if (sessions_.count(target)) return false; // already running
224
+ if (in_cooldown(target)) return false; // recently gave up
225
+ if (sessions_.size() >= config_.max_sessions) return false;
226
+
227
+ Session session;
228
+ session.initiator = true;
229
+ session.phase = Phase::AwaitingPeerConnect;
230
+ session.deadline = std::chrono::steady_clock::now() + config_.round_timeout;
231
+ sessions_.emplace(target, std::move(session));
232
+ }
233
+
234
+ if (!send_connect(target, /*opening=*/true, nullptr)) {
235
+ {
236
+ std::lock_guard<std::mutex> lock(mutex_);
237
+ sessions_.erase(target); // nobody could carry it; nothing is in flight
238
+ }
239
+ escalate_to_relay(target);
240
+ return false;
241
+ }
242
+ LOG_DEBUG("punch", "Punch rendezvous started with " << target.short_hex());
243
+ return true;
244
+ }
245
+
246
+ bool HolePunch::send_connect(const PeerId& target, bool opening, const PeerId* via) {
247
+ const auto addresses = own_punch_addresses();
248
+ if (addresses.empty()) return false;
249
+ return relay_to(target, encode_connect(opening, addresses), via) > 0;
250
+ }
251
+
252
+ void HolePunch::send_sync(const PeerId& target, const PeerId* via) {
253
+ Bytes inner;
254
+ inner.push_back(kKindSync);
255
+ relay_to(target, inner, via);
256
+ }
257
+
258
+ size_t HolePunch::relay_to(const PeerId& dst, const Bytes& inner, const PeerId* via) {
259
+ const Bytes message = envelope(kOpRelay, dst, inner);
260
+
261
+ // A hop that has already carried something from the target is known to reach it,
262
+ // so the rest of the exchange goes back that way rather than out to everybody
263
+ // again. Only the very first message pays the fan-out.
264
+ if (via) {
265
+ network_->send(*via, MessageType::Punch, ByteView(message));
266
+ return 1;
267
+ }
268
+
269
+ // Nothing known yet: we do not know which of our peers also has the target, so
270
+ // ask a few and let the ones that cannot help drop it. Bounded on purpose —
271
+ // every extra relay is a duplicate rendezvous the far end has to recognise.
272
+ size_t sent = 0;
273
+ for (const PeerId& relay : network_->connected_peers()) {
274
+ if (sent >= config_.max_relays) break;
275
+ if (relay == dst) continue; // if it were connected we would not be punching
276
+ // send() answers backpressure, not delivery, and every id here is a peer we
277
+ // hold — so the message is on its way either way.
278
+ network_->send(relay, MessageType::Punch, ByteView(message));
279
+ ++sent;
280
+ }
281
+ return sent;
282
+ }
283
+
284
+ std::vector<Address> HolePunch::own_punch_addresses() const {
285
+ if (!external_) return {};
286
+ auto addresses = external_->external_udp_endpoints();
287
+ if (addresses.size() > config_.max_addresses) addresses.resize(config_.max_addresses);
288
+ return addresses;
289
+ }
290
+
291
+ // ── Inbound ─────────────────────────────────────────────────────────────────
292
+
293
+ void HolePunch::handle(const Peer& from, ByteView payload) {
294
+ if (!running_.load()) return;
295
+ if (payload.size() < 2 + PeerId::kSize || payload.size() > kMaxMessageSize) return;
296
+ if (payload.data()[0] != kVersion) return;
297
+
298
+ const uint8_t op = payload.data()[1];
299
+ const auto id = PeerId::from_bytes(ByteView(payload.data() + 2, PeerId::kSize));
300
+ if (!id) return;
301
+ const ByteView inner(payload.data() + 2 + PeerId::kSize,
302
+ payload.size() - 2 - PeerId::kSize);
303
+ if (inner.empty()) return;
304
+
305
+ if (op == kOpRelay) {
306
+ handle_relay_request(from, *id, inner);
307
+ } else if (op == kOpRelayed) {
308
+ // The envelope names who this came from, but only the peer that actually
309
+ // delivered it vouches for anything — and it vouches only for having
310
+ // forwarded it. That is the whole trust model here; see the note in the
311
+ // header about what acting on a forged Connect can cost.
312
+ handle_relayed(from, *id, inner);
313
+ }
314
+ }
315
+
316
+ void HolePunch::handle_relay_request(const Peer& from, const PeerId& dst, ByteView inner) {
317
+ if (!config_.enable_relay) return;
318
+ if (dst == network_->local_id() || dst == from.id()) return;
319
+
320
+ // Forward ONLY to a peer we already hold. This is what keeps a relay from being
321
+ // an open reflector: it never resolves an address, never dials, and can only
322
+ // ever deliver to somebody that chose to connect to it. Checked before the
323
+ // budget is spent, so the common case — a fan-out request for a peer we simply
324
+ // do not have — costs the sender nothing.
325
+ const std::vector<PeerId> peers = network_->connected_peers();
326
+ if (std::find(peers.begin(), peers.end(), dst) == peers.end()) return;
327
+
328
+ if (!relay_budget_ok(from.id())) {
329
+ LOG_DEBUG("punch", "Dropping relay from " << from.id().short_hex() << ": over budget");
330
+ return;
331
+ }
332
+
333
+ const Bytes message = envelope(kOpRelayed, from.id(), Bytes(inner.begin(), inner.end()));
334
+ network_->send(dst, MessageType::Punch, ByteView(message));
335
+ relayed_.fetch_add(1, std::memory_order_relaxed);
336
+ }
337
+
338
+ void HolePunch::handle_relayed(const Peer& via, const PeerId& src, ByteView inner) {
339
+ if (src == network_->local_id()) return; // a relay looping our own message back
340
+
341
+ const uint8_t kind = inner.data()[0];
342
+ const ByteView body(inner.data() + 1, inner.size() - 1);
343
+
344
+ if (kind == kKindConnect) {
345
+ auto connect = decode_connect(body);
346
+ if (!connect) return; // malformed → ignore, never fatal
347
+ handle_connect(via, src, connect->opening, std::move(connect->addresses));
348
+ } else if (kind == kKindSync) {
349
+ handle_sync(src);
350
+ }
351
+ }
352
+
353
+ void HolePunch::handle_connect(const Peer& via, const PeerId& src, bool opening,
354
+ std::vector<Address> addresses) {
355
+ if (addresses.empty()) return;
356
+ if (addresses.size() > config_.max_addresses) addresses.resize(config_.max_addresses);
357
+
358
+ bool reply_with_connect = false;
359
+ bool punch_now = false;
360
+ std::vector<Address> targets;
361
+ const PeerId hop = via.id();
362
+
363
+ {
364
+ std::lock_guard<std::mutex> lock(mutex_);
365
+ auto it = sessions_.find(src);
366
+
367
+ if (!opening) {
368
+ // The answer to a rendezvous WE opened. Anything else — an answer with
369
+ // no question, or one arriving after we stood down — is stale and is
370
+ // dropped rather than acted on.
371
+ if (it == sessions_.end() || !it->second.initiator) return;
372
+ if (it->second.phase != Phase::AwaitingPeerConnect) return;
373
+ } else if (it != sessions_.end() && it->second.initiator) {
374
+ // Both ends opened a rendezvous at once. Left alone each would wait for
375
+ // the other's Sync and neither would send one, so the same symmetric
376
+ // rule the peer table uses for a cross-connect settles it: the smaller
377
+ // id keeps the round, the larger stands down and answers. Both ends
378
+ // compute it identically, so exactly one of them stands down.
379
+ if (network_->local_id() < src) return; // ours wins; ignore theirs
380
+ it->second.initiator = false;
381
+ }
382
+
383
+ if (it == sessions_.end()) {
384
+ if (sessions_.size() >= config_.max_sessions) return;
385
+ if (in_cooldown(src)) return;
386
+ Session session;
387
+ session.initiator = false;
388
+ it = sessions_.emplace(src, std::move(session)).first;
389
+ }
390
+
391
+ Session& session = it->second;
392
+ session.peer_addresses = std::move(addresses);
393
+ session.deadline = std::chrono::steady_clock::now() + config_.round_timeout;
394
+ session.via = hop; // this hop just proved it reaches the target
395
+ session.have_via = true;
396
+
397
+ if (session.initiator) {
398
+ // Step 3: the round trip through the relay is now known, so send Sync
399
+ // and start dialing immediately — the peer starts when Sync lands, and
400
+ // the two bursts meet in the middle.
401
+ session.phase = Phase::Punching;
402
+ punch_now = true;
403
+ targets = session.peer_addresses;
404
+ } else {
405
+ session.phase = Phase::AwaitingSync;
406
+ reply_with_connect = true;
407
+ }
408
+ }
409
+
410
+ // Outside the lock: both reach the network, and dialing may run callbacks inline.
411
+ if (reply_with_connect) {
412
+ if (!send_connect(src, /*opening=*/false, &hop)) {
413
+ std::lock_guard<std::mutex> lock(mutex_);
414
+ sessions_.erase(src);
415
+ }
416
+ return;
417
+ }
418
+ if (punch_now) {
419
+ // Order matters: Sync goes first, then we dial. The peer starts dialing when
420
+ // Sync lands, so leaving here first is what buys the half round trip that
421
+ // makes the two bursts overlap.
422
+ send_sync(src, &hop);
423
+ fire_punch(src, targets);
424
+ }
425
+ }
426
+
427
+ void HolePunch::handle_sync(const PeerId& src) {
428
+ std::vector<Address> targets;
429
+ {
430
+ std::lock_guard<std::mutex> lock(mutex_);
431
+ auto it = sessions_.find(src);
432
+ if (it == sessions_.end() || it->second.phase != Phase::AwaitingSync) return;
433
+ if (it->second.peer_addresses.empty()) return;
434
+ it->second.phase = Phase::Punching;
435
+ it->second.deadline = std::chrono::steady_clock::now() + config_.round_timeout;
436
+ targets = it->second.peer_addresses;
437
+ }
438
+ fire_punch(src, targets);
439
+ }
440
+
441
+ void HolePunch::fire_punch(const PeerId& target, const std::vector<Address>& addresses) {
442
+ if (!dialer_) return;
443
+ LOG_DEBUG("punch", "Punching to " << target.short_hex() << " at " << addresses.size()
444
+ << " endpoint(s)");
445
+ for (const Address& addr : addresses)
446
+ dialer_->dial_direct(addr, TransportKind::Udp, config_.profile);
447
+ punches_started_.fetch_add(1, std::memory_order_relaxed);
448
+ }
449
+
450
+ // ── Relay budget ────────────────────────────────────────────────────────────
451
+
452
+ bool HolePunch::relay_budget_ok(const PeerId& from) {
453
+ const auto now = std::chrono::steady_clock::now();
454
+ std::lock_guard<std::mutex> lock(relay_mutex_);
455
+
456
+ // Bounded by dropping the whole table when it grows past what a real mesh would
457
+ // hold. Cheaper than per-entry expiry on a path that runs per forwarded message,
458
+ // and the only cost of the reset is one over-generous window for everybody.
459
+ if (relay_budget_.size() > 4096) relay_budget_.clear();
460
+
461
+ RelayBudget& budget = relay_budget_[from];
462
+ if (now - budget.window_started >= config_.relay_window) {
463
+ budget.window_started = now;
464
+ budget.spent = 0;
465
+ }
466
+ if (budget.spent >= config_.relay_budget) return false;
467
+ ++budget.spent;
468
+ return true;
469
+ }
470
+
471
+ // ── Worker ──────────────────────────────────────────────────────────────────
472
+
473
+ void HolePunch::loop() {
474
+ std::unique_lock<std::mutex> lock(mutex_);
475
+ while (running_.load()) {
476
+ cv_.wait_for(lock, config_.tick, [this] { return !running_.load(); });
477
+ if (!running_.load()) return;
478
+ lock.unlock();
479
+ service_sessions();
480
+ lock.lock();
481
+ }
482
+ }
483
+
484
+ void HolePunch::service_sessions() {
485
+ const auto now = std::chrono::steady_clock::now();
486
+
487
+ // Connected peers first, without the lock: a session whose target is now a peer
488
+ // succeeded, however it got there (our burst, or theirs arriving as inbound).
489
+ // Direct links only — a relayed one is what a punch is trying to replace.
490
+ std::vector<PeerId> connected = directly_connected();
491
+
492
+ struct Retry { PeerId target; PeerId via; bool have_via; };
493
+ std::vector<Retry> retry;
494
+ std::vector<PeerId> exhausted; ///< targets to hand on to the relay, once unlocked
495
+ {
496
+ std::lock_guard<std::mutex> lock(mutex_);
497
+
498
+ for (auto it = cooldown_.begin(); it != cooldown_.end();) {
499
+ if (now >= it->second) it = cooldown_.erase(it);
500
+ else ++it;
501
+ }
502
+
503
+ for (auto it = sessions_.begin(); it != sessions_.end();) {
504
+ const PeerId& target = it->first;
505
+ if (std::find(connected.begin(), connected.end(), target) != connected.end()) {
506
+ LOG_DEBUG("punch", "Punch to " << target.short_hex() << " succeeded");
507
+ it = sessions_.erase(it);
508
+ continue;
509
+ }
510
+ if (now < it->second.deadline) { ++it; continue; }
511
+
512
+ // The round is over and the peer is still not here. Only the initiator
513
+ // retries: a responder has no idea whether the other side is still
514
+ // trying, and two ends retrying independently would drift apart from the
515
+ // one thing that makes a punch work — starting together.
516
+ const bool initiator = it->second.initiator;
517
+ if (!initiator || ++it->second.attempt >= config_.attempts) {
518
+ LOG_DEBUG("punch", "Giving up on " << target.short_hex() << " for now");
519
+ // Cooldown is the INITIATOR's verdict on a target and nobody else's.
520
+ // A responder round ending means only that the Sync never came — the
521
+ // initiator is very likely mid-retry at that exact moment (its round
522
+ // started a relay hop earlier than ours, so its deadline expires
523
+ // first). Calling the target off here would drop the retries it is
524
+ // about to send, and block this node's own later punch to it, for
525
+ // the whole cooldown. So a responder just forgets the round.
526
+ if (initiator) {
527
+ begin_cooldown(target);
528
+ exhausted.push_back(target);
529
+ }
530
+ it = sessions_.erase(it);
531
+ continue;
532
+ }
533
+ it->second.phase = Phase::AwaitingPeerConnect;
534
+ it->second.peer_addresses.clear();
535
+ it->second.deadline = now + config_.round_timeout;
536
+ retry.push_back(Retry{target, it->second.via, it->second.have_via});
537
+ ++it;
538
+ }
539
+ }
540
+
541
+ for (const Retry& r : retry) {
542
+ if (send_connect(r.target, /*opening=*/true, r.have_via ? &r.via : nullptr)) continue;
543
+ {
544
+ std::lock_guard<std::mutex> lock(mutex_);
545
+ sessions_.erase(r.target);
546
+ }
547
+ exhausted.push_back(r.target);
548
+ }
549
+
550
+ // The rungs above this one are spent: no address we can advertise got through.
551
+ // Handing the target on is the difference between a peer that is unreachable
552
+ // and one that is merely expensive to reach.
553
+ for (const PeerId& target : exhausted) escalate_to_relay(target);
554
+ }
555
+
556
+ void HolePunch::escalate_to_relay(const PeerId& target) {
557
+ RelayService* relay = relay_.load();
558
+ if (!relay) return; // no Relay attached, or the fallback is off
559
+ if (!relay->connect_via_relay(target)) return;
560
+
561
+ LOG_DEBUG("punch", "Punching to " << target.short_hex()
562
+ << " is not going to work; looking for a relay instead");
563
+
564
+ // The target has just changed hands, and the cooldown a give-up started must not
565
+ // outlive that. It exists to stop us hammering a peer we cannot reach — a job
566
+ // the relay now owns, with an attempt timeout and a cooldown of its own.
567
+ //
568
+ // And if that attempt lands, the circuit is precisely the new information that
569
+ // makes another punch worth trying: the two ends are peers at last, so the
570
+ // rendezvous can travel over the very circuit carrying them, which is what
571
+ // Relay asks for the moment the circuit comes up (see subsystems/relay.h).
572
+ // Leaving the cooldown standing would refuse that upgrade before it started —
573
+ // and nothing would ever ask again, so the circuit would outlive its purpose and
574
+ // keep costing a third node bandwidth for the life of the peer.
575
+ //
576
+ // Bounded, not a loop: an upgrade punch that fails lands here again, and
577
+ // connect_via_relay then answers false — the target is already a peer — so the
578
+ // fresh cooldown stands. Taken after the call, never around it: Relay resolves
579
+ // the reverse direction through us, and this mutex must not be held into it.
580
+ std::lock_guard<std::mutex> lock(mutex_);
581
+ cooldown_.erase(target);
582
+ }
583
+
584
+ std::vector<PeerId> HolePunch::directly_connected() const {
585
+ std::vector<PeerId> ids;
586
+ for (const PeerInfo& info : network_->peers())
587
+ if (info.transport != TransportKind::Relay) ids.push_back(info.id);
588
+ return ids;
589
+ }
590
+
591
+ bool HolePunch::in_cooldown(const PeerId& target) const {
592
+ auto it = cooldown_.find(target);
593
+ return it != cooldown_.end() && std::chrono::steady_clock::now() < it->second;
594
+ }
595
+
596
+ void HolePunch::begin_cooldown(const PeerId& target) {
597
+ cooldown_[target] = std::chrono::steady_clock::now() + config_.cooldown;
598
+ }
599
+
600
+ size_t HolePunch::active_sessions() const {
601
+ std::lock_guard<std::mutex> lock(mutex_);
602
+ return sessions_.size();
603
+ }
604
+
605
+ } // namespace librats