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