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,96 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file link.h
5
+ * @brief The byte-stream a Connection runs on, independent of how it is carried.
6
+ *
7
+ * Everything above this line — block framing, the Noise handshake, the Session,
8
+ * backpressure, the establish deadline — only ever needs an ordered, reliable,
9
+ * non-blocking byte stream. A Link is exactly that and nothing more, so the same
10
+ * Connection code drives a kernel TCP socket (TcpLink) and the library's own
11
+ * reliability layer over datagrams (UdpStreamLink → UdpStream). Neither transport
12
+ * is a special case of the other: both are first-class, and a peer connected over
13
+ * either is indistinguishable to every layer above.
14
+ *
15
+ * Ownership and threading follow the rest of the transport layer: a Link belongs
16
+ * to exactly one Connection, which belongs to exactly one Reactor, and is touched
17
+ * only by that reactor's thread. It therefore holds no locks and no atomics.
18
+ *
19
+ * The read/write calls mirror non-blocking socket semantics deliberately, because
20
+ * that is what the reactor loop is built around:
21
+ * - read() fills the caller's span and reports WouldBlock once the link has no
22
+ * more data ready, or Closed when the peer has finished sending.
23
+ * - write() takes what it can and reports WouldBlock when it can take no more;
24
+ * the Connection keeps the remainder queued and calls want_write(true)
25
+ * so it is told when to retry.
26
+ */
27
+
28
+ #include "librats/core/address.h"
29
+ #include "librats/core/bytes.h"
30
+ #include "librats/core/socket.h"
31
+ #include "librats/core/types.h"
32
+
33
+ #include <cstddef>
34
+ #include <optional>
35
+
36
+ namespace librats {
37
+
38
+ class Link {
39
+ public:
40
+ virtual ~Link() = default;
41
+
42
+ /// Outcome of a read/write attempt. `Closed` is orderly (the peer is done
43
+ /// sending); `Error` means the link is broken and must be torn down.
44
+ enum class Status { Ok, WouldBlock, Closed, Error };
45
+
46
+ struct IoResult {
47
+ size_t bytes = 0;
48
+ Status status = Status::WouldBlock;
49
+ };
50
+
51
+ virtual TransportKind kind() const noexcept = 0;
52
+
53
+ /// Read up to `into.size()` bytes. A Closed or Error result always carries
54
+ /// zero bytes — data and end-of-stream are never reported together, so the
55
+ /// caller drains first and only then sees the close.
56
+ virtual IoResult read(ByteSpan into) = 0;
57
+
58
+ /// Hand `count` contiguous slices to the link in order, writing as many bytes
59
+ /// as it will take (a partial write is normal and expected).
60
+ virtual IoResult write(const ByteView* slices, size_t count) = 0;
61
+
62
+ /// Called once the link reports writable while the connection is still
63
+ /// connecting. Returns false if the connect attempt failed.
64
+ virtual bool connect_completed() = 0;
65
+
66
+ /// Ask to be woken (via Connection::on_writable) when more can be written.
67
+ /// Called only when the state actually changes, so an implementation may
68
+ /// treat it as an unconditional set.
69
+ virtual void want_write(bool on) = 0;
70
+
71
+ /// The peer's endpoint. For TCP the port is the peer's *ephemeral* source
72
+ /// port, so only the IP is meaningful (see identify); for UDP it is the
73
+ /// endpoint datagrams are exchanged with. nullopt if not yet known.
74
+ virtual std::optional<Address> remote_endpoint() const = 0;
75
+
76
+ /// The kernel socket backing this link, or RATS_INVALID_SOCKET when the link
77
+ /// has none of its own (a UDP stream shares the mux's socket with every other
78
+ /// stream). The Reactor uses it to decide whether the link is poller-driven.
79
+ virtual socket_t fd() const noexcept { return RATS_INVALID_SOCKET; }
80
+
81
+ /// Told the ConnId the Reactor assigned, right after adoption. A link that has
82
+ /// to be reachable from somewhere other than its Connection (a UDP stream, from
83
+ /// the datagram path) needs it; a plain socket does not.
84
+ virtual void attach(ConnId) {}
85
+
86
+ /// Why the link broke, consulted when read/write reported Error. Lets a
87
+ /// transport that knows more than "the socket failed" — a dial that was never
88
+ /// answered, a peer that went silent — say so.
89
+ virtual CloseReason error_reason() const { return CloseReason::PeerReset; }
90
+
91
+ /// Begin an orderly shutdown. Called by the Reactor as it tears the connection
92
+ /// down; the Link may still linger internally to flush what it owes the peer.
93
+ virtual void close(CloseReason reason) = 0;
94
+ };
95
+
96
+ } // namespace librats
@@ -0,0 +1,540 @@
1
+ #include "librats/transport/reactor.h"
2
+ #include "librats/transport/tcp_link.h"
3
+ #include "librats/core/ip_address.h"
4
+ #include "librats/util/logger.h"
5
+ #include "librats/util/network_utils.h"
6
+
7
+ #include <cstring>
8
+ #include <utility>
9
+
10
+ namespace librats {
11
+
12
+ namespace {
13
+
14
+ /// Turn a dial target into the numeric endpoint the datagram transport needs.
15
+ /// A literal is used as-is; a hostname is resolved in the order this socket can
16
+ /// actually use.
17
+ ///
18
+ /// The order matters as much as the result. One mux socket serves every peer and
19
+ /// is bound in one family, which the default configuration makes IPv4 — so asking
20
+ /// for AAAA first, as TCP can afford to, would keep handing back addresses this
21
+ /// socket cannot send to while the same host advertises a perfectly dialable A
22
+ /// record. Answering with a reachable address is the whole job here.
23
+ std::optional<Address> resolve_dial_target(const std::string& host, int port,
24
+ AddressFamily af) {
25
+ if (auto ip = IpAddress::parse(host))
26
+ return Address{*ip, static_cast<uint16_t>(port)};
27
+
28
+ if (af != AddressFamily::IPv4) {
29
+ if (auto ip = IpAddress::parse(network_utils::resolve_hostname_v6(host)))
30
+ return Address{*ip, static_cast<uint16_t>(port)};
31
+ }
32
+ if (af != AddressFamily::IPv6) {
33
+ if (auto ip = IpAddress::parse(network_utils::resolve_hostname(host)))
34
+ return Address{*ip, static_cast<uint16_t>(port)};
35
+ }
36
+ return std::nullopt;
37
+ }
38
+
39
+ } // namespace
40
+
41
+ Reactor::Reactor(uint8_t index, ConnectionDelegate& delegate, SecurityProvider& security)
42
+ : index_(index), delegate_(delegate), security_(security), poller_(IOPoller::create()) {}
43
+
44
+ Reactor::~Reactor() {
45
+ stop();
46
+ }
47
+
48
+ // ── Lifecycle ───────────────────────────────────────────────────────────────
49
+
50
+ void Reactor::listen(socket_t server_socket) {
51
+ server_socket_ = server_socket;
52
+ }
53
+
54
+ void Reactor::listen_udp(socket_t udp_socket, AddressFamily family) {
55
+ mux_ = std::make_unique<UdpMux>(udp_socket, family, *this);
56
+ }
57
+
58
+ void Reactor::start() {
59
+ if (running_.exchange(true)) return;
60
+ thread_ = std::thread(&Reactor::run, this);
61
+ }
62
+
63
+ void Reactor::stop() {
64
+ if (!running_.exchange(false)) {
65
+ if (thread_.joinable()) thread_.join();
66
+ return;
67
+ }
68
+ wakeup_.signal(); // break the poll wait
69
+ if (thread_.joinable()) thread_.join();
70
+ }
71
+
72
+ // ── Work submission ─────────────────────────────────────────────────────────
73
+
74
+ bool Reactor::on_reactor_thread() const noexcept {
75
+ return std::this_thread::get_id() == thread_id_.load(std::memory_order_acquire);
76
+ }
77
+
78
+ void Reactor::post(Task task) {
79
+ // After stop() the loop has exited and (apart from the brief stopping window
80
+ // caught by the final drain in run()) the task will never run. Enqueue anyway
81
+ // so its captures are released at destruction, but flag the dropped work.
82
+ if (!running_.load(std::memory_order_acquire))
83
+ LOG_DEBUG("reactor", "Reactor " << static_cast<int>(index_)
84
+ << " post() after stop; task may not run");
85
+ tasks_.push(std::move(task));
86
+ wakeup_.signal();
87
+ }
88
+
89
+ void Reactor::execute(Task task) {
90
+ if (on_reactor_thread()) task();
91
+ else post(std::move(task));
92
+ }
93
+
94
+ ConnId Reactor::connect(std::string host, int port, TransportKind kind, DialProfile profile) {
95
+ if (kind == TransportKind::Udp && !mux_) {
96
+ LOG_DEBUG("reactor", "Reactor " << static_cast<int>(index_)
97
+ << " has no datagram transport; refusing UDP dial to " << host << ":" << port);
98
+ return kInvalidConnId;
99
+ }
100
+
101
+ // Reserve the id here, on the calling thread, rather than inside the task. The
102
+ // caller can then cancel this exact attempt (close()) before the task has even
103
+ // run — which is what makes racing one transport against the other cancellable
104
+ // instead of a matter of luck.
105
+ const ConnId id = next_conn_id_.fetch_add(1, std::memory_order_relaxed);
106
+ post([this, host = std::move(host), port, kind, profile, id] {
107
+ start_dial(id, host, port, kind, profile);
108
+ });
109
+ return id;
110
+ }
111
+
112
+ void Reactor::start_dial(ConnId id, const std::string& host, int port, TransportKind kind,
113
+ DialProfile profile) {
114
+ if (kind == TransportKind::Udp) {
115
+ const auto target = resolve_dial_target(host, port, mux_->family());
116
+ if (!target) {
117
+ LOG_DEBUG("reactor", "Cannot resolve " << host << " for a UDP dial");
118
+ abort_dial(id, host, port);
119
+ return;
120
+ }
121
+ // The one mux socket cannot leave its own address family, and that is
122
+ // knowable here rather than after the fact. Saying so now costs nothing —
123
+ // the dialer brings the other transport in at once. Opening the stream
124
+ // anyway would spend the Syn's whole give-up on datagrams the kernel
125
+ // refuses one by one, and refusals at that layer are per-packet and
126
+ // silent: a dial that never had a chance would be indistinguishable from
127
+ // a peer that is merely slow to answer.
128
+ if (!family_can_reach(mux_->family(), target->ip.is_v6())) {
129
+ LOG_DEBUG("reactor", "Datagram socket cannot reach " << target->to_string()
130
+ << "; leaving " << host << ":" << port << " to the other transport");
131
+ abort_dial(id, host, port);
132
+ return;
133
+ }
134
+ std::unique_ptr<Link> link = mux_->connect(*target, profile);
135
+ if (!link) { abort_dial(id, host, port); return; }
136
+
137
+ Connection* conn = adopt(std::move(link), ConnRole::Outbound, id);
138
+ if (conn) conn->set_dial_address(host, static_cast<uint16_t>(port));
139
+ return;
140
+ }
141
+
142
+ socket_t sock = tcp_connect_start(host, port);
143
+ if (!is_valid_socket(sock)) {
144
+ LOG_DEBUG("reactor", "Outbound connect to " << host << ":" << port << " failed to start");
145
+ abort_dial(id, host, port);
146
+ return;
147
+ }
148
+ Connection* conn = adopt(std::make_unique<TcpLink>(sock, *this), ConnRole::Outbound, id);
149
+ if (!conn) { close_socket(sock); return; } // the dial was cancelled while queued
150
+ conn->set_dial_address(host, static_cast<uint16_t>(port));
151
+ }
152
+
153
+ void Reactor::abort_dial(ConnId id, const std::string& host, int port) {
154
+ resolve_dial(id); // the id is spent either way; release any cancellation slot
155
+ delegate_.on_dial_aborted(index_, id, host, static_cast<uint16_t>(port));
156
+ }
157
+
158
+ void Reactor::close(ConnId id, CloseReason reason) {
159
+ close_conn(id, reason, /*spare_established=*/false);
160
+ }
161
+
162
+ void Reactor::cancel_dial(ConnId id, CloseReason reason) {
163
+ close_conn(id, reason, /*spare_established=*/true);
164
+ }
165
+
166
+ void Reactor::close_conn(ConnId id, CloseReason reason, bool spare_established) {
167
+ execute([this, id, reason, spare_established] {
168
+ auto it = conns_.find(id);
169
+ if (it != conns_.end()) {
170
+ // It got there first: the dial it was is over, and what it left behind
171
+ // is a connection to a peer. Whether that one or another link survives
172
+ // is decided by the peer table on both ends alike — see peer_table.cpp.
173
+ if (spare_established && it->second->state() == ConnState::Established) return;
174
+ mark_for_close(id, reason);
175
+ return;
176
+ }
177
+ // Not adopted yet: its connect task is still queued behind this one. Leave
178
+ // a note so adopt() drops it on arrival. Ids at or below the watermark have
179
+ // already come and gone, so they need no slot (and cannot leak one).
180
+ if (id != kInvalidConnId && id > resolved_watermark_) cancelled_dials_.insert(id);
181
+ });
182
+ }
183
+
184
+ void Reactor::broadcast(FrameHeader header, std::shared_ptr<const Bytes> payload) {
185
+ execute([this, header, payload = std::move(payload)] {
186
+ for (auto& [id, conn] : conns_) {
187
+ if (conn->state() == ConnState::Established)
188
+ conn->send(header, ByteView(*payload));
189
+ }
190
+ });
191
+ }
192
+
193
+ // ── Timers ──────────────────────────────────────────────────────────────────
194
+
195
+ TimerId Reactor::schedule(std::chrono::milliseconds delay, Task on_fire) {
196
+ return timers_.schedule(delay, std::move(on_fire));
197
+ }
198
+
199
+ void Reactor::cancel(TimerId id) {
200
+ timers_.cancel(id);
201
+ }
202
+
203
+ // ── Lookups / interest ──────────────────────────────────────────────────────
204
+
205
+ Connection* Reactor::find(ConnId id) noexcept {
206
+ auto it = conns_.find(id);
207
+ return it == conns_.end() ? nullptr : it->second.get();
208
+ }
209
+
210
+ void Reactor::set_interest(socket_t sock, uint32_t events) {
211
+ poller_->modify(sock, events);
212
+ }
213
+
214
+ // ── Reactor loop ────────────────────────────────────────────────────────────
215
+
216
+ void Reactor::run() {
217
+ thread_id_ = std::this_thread::get_id();
218
+ LOG_INFO("reactor", "Reactor " << static_cast<int>(index_)
219
+ << " started (backend: " << poller_->name() << ")");
220
+
221
+ set_socket_nonblocking(wakeup_.fd());
222
+ poller_->add(wakeup_.fd(), PollIn);
223
+ if (is_valid_socket(server_socket_)) {
224
+ set_socket_nonblocking(server_socket_);
225
+ poller_->add(server_socket_, PollIn);
226
+ }
227
+ if (mux_) {
228
+ set_socket_nonblocking(mux_->socket());
229
+ poller_->add(mux_->socket(), PollIn);
230
+ }
231
+
232
+ PollResult events[kMaxEvents];
233
+ std::vector<Task> task_batch;
234
+
235
+ schedule_maintenance();
236
+
237
+ while (running_.load(std::memory_order_relaxed)) {
238
+ int timeout = timers_.next_timeout_ms(kMaxPollMs);
239
+ // The datagram transport keeps its own deadlines — retransmission timeouts,
240
+ // delayed acks, keep-alives — rather than being swept on a fixed cadence, so
241
+ // the wait has to cover the earliest of them too. With no stream due it
242
+ // contributes nothing, and an idle mux costs no wake-ups at all.
243
+ if (mux_) timeout = mux_->next_timeout_ms(timeout);
244
+ const int n = poller_->wait(events, kMaxEvents, timeout);
245
+
246
+ drain_tasks(task_batch); // connect/close/send-arm
247
+ for (int i = 0; i < n; ++i) handle_event(events[i]);
248
+ timers_.run_due();
249
+ // Service whatever datagram deadlines have come due. Deliberately not a
250
+ // TimerQueue entry: those deadlines move on nearly every packet, and
251
+ // re-scheduling a timer that often would cost far more than the one
252
+ // comparison this is when nothing is due. Before process_pending_close(),
253
+ // so a stream that dies here is torn down in the same turn.
254
+ if (mux_) mux_->tick();
255
+ // Everything queued by send() during this turn goes to the wire here, in
256
+ // one write per connection rather than one per frame. Deliberately after
257
+ // the mux tick — a handler it dispatched may well have sent something —
258
+ // and before the close pass, so a connection that dies on the write is
259
+ // torn down in the same turn.
260
+ flush_dirty();
261
+ process_pending_close();
262
+ // The mux batches its datagrams; the ones it collected inside a readable
263
+ // event or a tick have already gone out, but an application send reaches a
264
+ // UDP stream from a task, outside any batch of its own. Flushing once here
265
+ // covers those without giving any of them a timer to wait on.
266
+ if (mux_) mux_->flush_output();
267
+ }
268
+
269
+ // Graceful drain: run whatever was queued before/around stop() so in-flight
270
+ // sends flush and explicit closes settle, then tear everything down. (post()
271
+ // may still race in after this; such tasks are dropped — see post().)
272
+ drain_tasks(task_batch);
273
+ flush_dirty(); // in-flight sends leave before the door closes
274
+ process_pending_close();
275
+ shutdown_connections();
276
+ if (mux_) mux_->shutdown(); // after the connections, so no Link outlives it
277
+
278
+ // Timers outlive the loop that scheduled them unless the heap goes with it,
279
+ // and schedule_maintenance() re-arms itself: an entry left behind would start
280
+ // a second self-perpetuating chain on the next run(), so N restarts would cost
281
+ // N+1 sweeps per interval and N+1 timed wake-ups, for as long as the node
282
+ // lives. Whatever else is still pending — establish deadlines, backoffs —
283
+ // belongs to connections that no longer exist.
284
+ timers_.clear();
285
+
286
+ // Hand back the sockets this loop registered above. A registration belongs to a
287
+ // descriptor, and a stopped reactor can be started again on freshly opened ones
288
+ // — which the kernel is free to hand the very same descriptor numbers, and on
289
+ // the BSDs, which allocate the lowest free one, very nearly always does. The
290
+ // backends that keep a registration table of their own (kqueue, poll, IOCP)
291
+ // would then refuse to add a descriptor they believe is already watched, while
292
+ // the kernel forgot it the moment it was closed — leaving a restarted node
293
+ // polling nothing at all: no accepts, no datagrams, every dial to it timing out.
294
+ // epoll drops a closed descriptor by itself, which is why this only ever showed
295
+ // on macOS.
296
+ //
297
+ // The listen sockets themselves are the node's to close, and it does so after
298
+ // joining this thread — so they are still open here, which is exactly when a
299
+ // registration can still be withdrawn cleanly.
300
+ poller_->remove(wakeup_.fd());
301
+ if (is_valid_socket(server_socket_)) {
302
+ poller_->remove(server_socket_);
303
+ // Forgotten as well as unwatched: the next run() must not re-add a
304
+ // descriptor the node closed in between, which by then may name something
305
+ // else entirely. listen() hands over the new one before every start.
306
+ server_socket_ = RATS_INVALID_SOCKET;
307
+ }
308
+ if (mux_) {
309
+ poller_->remove(mux_->socket());
310
+ mux_.reset();
311
+ }
312
+
313
+ LOG_INFO("reactor", "Reactor " << static_cast<int>(index_) << " stopped");
314
+ }
315
+
316
+ void Reactor::drain_tasks(std::vector<Task>& scratch) {
317
+ tasks_.drain(scratch);
318
+ for (auto& task : scratch) task();
319
+ }
320
+
321
+ void Reactor::drain_wakeup() {
322
+ wakeup_.drain();
323
+ }
324
+
325
+ void Reactor::handle_event(const PollResult& ev) {
326
+ const socket_t fd = ev.fd;
327
+
328
+ if (fd == wakeup_.fd()) { drain_wakeup(); return; }
329
+ if (fd == server_socket_) { if (ev.events & PollIn) do_accept(); return; }
330
+ if (mux_ && fd == mux_->socket()) {
331
+ // Any event, not just PollIn. An error on a datagram socket belongs to one
332
+ // datagram — Windows reports an ICMP complaint about an earlier send on the
333
+ // *next* receive — and not to the socket, which stays usable; the cure is
334
+ // therefore to read, which surfaces or clears it and drains whatever queued
335
+ // up behind it. Treating PollErr as "nothing to do" would strand every
336
+ // datagram after it, since nothing else here ever re-arms this socket.
337
+ //
338
+ // Stopped short of draining: come straight back rather than waiting for the
339
+ // next datagram to re-arm an edge-triggered poller.
340
+ if (mux_->on_readable()) wakeup_.signal();
341
+ return;
342
+ }
343
+
344
+ auto it = fd_to_conn_.find(fd);
345
+ if (it == fd_to_conn_.end()) return;
346
+ dispatch_events(it->second, ev.events);
347
+ }
348
+
349
+ void Reactor::dispatch_events(ConnId id, uint32_t events) {
350
+ Connection* conn = find(id);
351
+ if (!conn) return;
352
+ if (conn->state() == ConnState::Closing || conn->state() == ConnState::Closed) return;
353
+ // Condemned earlier this turn, just not torn down yet — process_pending_close()
354
+ // runs at the end of the turn, so events polled alongside the one that closed it
355
+ // would otherwise still be delivered, and the connection could change state
356
+ // *after* the decision to close it was taken against the state it had.
357
+ //
358
+ // Which is not merely wasted work on bytes about to be dropped. cancel_dial()
359
+ // spares an attempt that has established; let one finish its handshake here,
360
+ // just after that check found it still handshaking, and it reaches the peer
361
+ // table as a duplicate and supersedes the sibling that beat it — while itself
362
+ // still standing condemned. Both are then gone at the end of the turn and the
363
+ // peer is lost, which is the very failure the cancel/close split exists to
364
+ // prevent. The map is empty on every turn that closes nothing, nearly all of them.
365
+ if (!pending_close_.empty() && pending_close_.count(id)) return;
366
+
367
+ bool keep = true;
368
+ if (events & (PollErr | PollHup)) {
369
+ keep = conn->on_error();
370
+ } else {
371
+ if (keep && (events & PollIn)) keep = conn->on_readable();
372
+ if (keep && (events & PollOut)) keep = conn->on_writable();
373
+ }
374
+
375
+ if (!keep) mark_for_close(id, conn->close_reason());
376
+ }
377
+
378
+ void Reactor::dispatch_link_events(ConnId id, uint32_t events) {
379
+ dispatch_events(id, events);
380
+ }
381
+
382
+ void Reactor::schedule_maintenance() {
383
+ timers_.schedule(kMaintenanceInterval, [this] {
384
+ // Connections being torn down this tick are still in conns_; the sweep is
385
+ // read-only with respect to the map, and on_maintenance_tick() never closes.
386
+ for (auto& [id, conn] : conns_) conn->on_maintenance_tick();
387
+ schedule_maintenance();
388
+ });
389
+ }
390
+
391
+ void Reactor::do_accept() {
392
+ // Level-triggered: drain all pending connections this tick. Use a raw,
393
+ // non-logging accept — accept_client() logs an error on every EWOULDBLOCK,
394
+ // which is the normal "no more pending" signal here.
395
+ while (true) {
396
+ socket_t client = ::accept(server_socket_, nullptr, nullptr);
397
+ if (!is_valid_socket(client)) break; // EWOULDBLOCK / error → done this tick
398
+ suppress_sigpipe(client); // not inherited from the listener; raw accept()
399
+
400
+ // Admission control: refuse new inbound peers when at capacity, before
401
+ // paying any handshake cost. Keep draining the backlog so the listener
402
+ // doesn't stay readable. The precise per-peer cap is enforced again at
403
+ // on_established (this coarse gate can't yet know the remote's identity).
404
+ if (!delegate_.admit_inbound()) {
405
+ close_socket(client);
406
+ continue;
407
+ }
408
+
409
+ Connection* conn = adopt(std::make_unique<TcpLink>(client, *this), ConnRole::Inbound,
410
+ next_conn_id_.fetch_add(1, std::memory_order_relaxed));
411
+ if (!conn) { close_socket(client); continue; }
412
+ conn->start_handshake(); // accepted sockets are already connected
413
+ }
414
+ }
415
+
416
+ ConnId Reactor::adopt_inbound_link(std::unique_ptr<Link> link) {
417
+ // Same coarse admission gate the TCP listener applies, for the same reason:
418
+ // turn a flood away before any handshake work is done for it.
419
+ if (!delegate_.admit_inbound()) return kInvalidConnId;
420
+
421
+ const ConnId id = next_conn_id_.fetch_add(1, std::memory_order_relaxed);
422
+ Connection* conn = adopt(std::move(link), ConnRole::Inbound, id);
423
+ if (!conn) return kInvalidConnId;
424
+ conn->start_handshake(); // an accepted stream is already connected
425
+ return id;
426
+ }
427
+
428
+ bool Reactor::resolve_dial(ConnId id) {
429
+ if (id > resolved_watermark_) resolved_watermark_ = id;
430
+ return cancelled_dials_.erase(id) > 0;
431
+ }
432
+
433
+ Connection* Reactor::adopt(std::unique_ptr<Link> link, ConnRole role, ConnId id) {
434
+ // A dial cancelled while its task sat in the queue never becomes a connection;
435
+ // dropping `link` here is what actually calls the attempt off.
436
+ if (resolve_dial(id)) return nullptr;
437
+
438
+ const socket_t fd = link->fd();
439
+ if (is_valid_socket(fd)) {
440
+ set_socket_nonblocking(fd);
441
+ // Only a TCP link has a socket of its own, so this reaches exactly the
442
+ // sockets Nagle applies to. Safe to do here and nowhere else because the
443
+ // send queue now aggregates on our side — see set_tcp_nodelay().
444
+ set_tcp_nodelay(fd);
445
+ }
446
+
447
+ auto conn = std::make_unique<Connection>(id, std::move(link), role, *this, delegate_);
448
+ Connection* raw = conn.get();
449
+ raw->link().attach(id);
450
+
451
+ conns_.emplace(id, std::move(conn));
452
+ conn_count_.fetch_add(1, std::memory_order_relaxed);
453
+
454
+ if (is_valid_socket(fd)) {
455
+ fd_to_conn_.emplace(fd, id);
456
+ // Inbound sockets are connected: watch for readable. Outbound sockets are
457
+ // still connecting: watch for writable, which signals connect completion.
458
+ poller_->add(fd, role == ConnRole::Inbound ? PollIn : PollOut);
459
+ }
460
+
461
+ // Reap connections that never reach Established (stuck connect or handshake).
462
+ TimerId timer = timers_.schedule(kEstablishTimeout, [this, id] {
463
+ auto it = conns_.find(id);
464
+ if (it == conns_.end()) return;
465
+ const ConnState st = it->second->state();
466
+ if (st != ConnState::Established && st != ConnState::Closing && st != ConnState::Closed) {
467
+ mark_for_close(id, st == ConnState::Connecting ? CloseReason::ConnectFailed
468
+ : CloseReason::HandshakeFailed);
469
+ }
470
+ });
471
+ raw->set_establish_timer(timer);
472
+ return raw;
473
+ }
474
+
475
+ // ── Teardown ────────────────────────────────────────────────────────────────
476
+
477
+ void Reactor::mark_for_close(ConnId id, CloseReason reason) {
478
+ pending_close_.emplace(id, reason); // first reason wins
479
+ }
480
+
481
+ void Reactor::flush_dirty() {
482
+ if (dirty_.empty()) return;
483
+ // Swapped out first: a flush can close a connection, and a close can queue
484
+ // further work — none of which should extend the batch being walked.
485
+ std::vector<ConnId> batch;
486
+ batch.swap(dirty_);
487
+ for (const ConnId id : batch) {
488
+ Connection* conn = find(id);
489
+ if (!conn) continue; // gone since it booked
490
+ if (conn->state() != ConnState::Established) continue; // closing; nothing to owe
491
+ if (!conn->flush_pending()) mark_for_close(id, conn->close_reason());
492
+ }
493
+ }
494
+
495
+ void Reactor::process_pending_close() {
496
+ if (pending_close_.empty()) return;
497
+ auto batch = std::move(pending_close_);
498
+ pending_close_.clear();
499
+ for (const auto& [id, reason] : batch) remove(id, reason);
500
+ }
501
+
502
+ void Reactor::remove(ConnId id, CloseReason reason) {
503
+ auto it = conns_.find(id);
504
+ if (it == conns_.end()) return;
505
+
506
+ std::unique_ptr<Connection> conn = std::move(it->second);
507
+ conns_.erase(it);
508
+
509
+ const socket_t fd = conn->socket();
510
+ if (is_valid_socket(fd)) {
511
+ fd_to_conn_.erase(fd);
512
+ poller_->remove(fd);
513
+ }
514
+ conn->cancel_establish_timer(); // stop the reaper before this fd can be reused
515
+ conn_count_.fetch_sub(1, std::memory_order_relaxed);
516
+
517
+ LOG_DEBUG("reactor", "Connection " << id << " closed (" << to_string(reason) << ")");
518
+ conn->shutdown_link(reason); // let the transport say goodbye / flush
519
+ delegate_.on_closed(*conn, reason);
520
+ if (is_valid_socket(fd)) close_socket(fd);
521
+ // ~Connection here releases the Link, which is what hands a UDP stream back to
522
+ // the mux (to linger briefly or go straight away).
523
+ }
524
+
525
+ void Reactor::shutdown_connections() {
526
+ for (auto& [id, conn] : conns_) {
527
+ const socket_t fd = conn->socket();
528
+ if (is_valid_socket(fd)) poller_->remove(fd);
529
+ conn->shutdown_link(CloseReason::ReactorShutdown);
530
+ delegate_.on_closed(*conn, CloseReason::ReactorShutdown);
531
+ if (is_valid_socket(fd)) close_socket(fd);
532
+ }
533
+ conns_.clear();
534
+ fd_to_conn_.clear();
535
+ pending_close_.clear();
536
+ cancelled_dials_.clear();
537
+ conn_count_.store(0, std::memory_order_relaxed);
538
+ }
539
+
540
+ } // namespace librats