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,617 @@
1
+ #include "librats/transport/udp_mux.h"
2
+ #include "librats/core/io_poller.h"
3
+ #include "librats/crypto/blake2s.h"
4
+ #include "librats/util/logger.h"
5
+
6
+ #include <algorithm>
7
+ #include <cstring>
8
+ #include <random>
9
+ #include <utility>
10
+
11
+ namespace librats {
12
+
13
+ namespace {
14
+
15
+ /// Fresh bytes for a cookie secret. Straight from the system source rather than
16
+ /// the mux's mt19937: that generator is seeded once and its output is predictable
17
+ /// from enough samples, which is the wrong property for the one value standing
18
+ /// between a forged source address and an allocation.
19
+ void fill_random(uint8_t* out, size_t len) {
20
+ std::random_device dev;
21
+ for (size_t i = 0; i < len; i += sizeof(unsigned int)) {
22
+ const unsigned int word = dev();
23
+ const size_t n = (std::min)(sizeof(word), len - i);
24
+ std::memcpy(out + i, &word, n);
25
+ }
26
+ }
27
+
28
+ } // namespace
29
+
30
+ UdpMux::UdpMux(socket_t socket, AddressFamily family, UdpMuxDelegate& delegate,
31
+ UdpMuxLimits limits)
32
+ : socket_(socket), family_(family), delegate_(delegate), limits_(limits),
33
+ recv_storage_(kUdpBatchMax * rudp::kMaxDatagram),
34
+ // Only where a batch is really one syscall. Without that, send_datagram()
35
+ // goes straight to the socket and never stages anything, so this would be
36
+ // 39 KiB per node that is allocated and never touched.
37
+ send_storage_(kUdpBatchIsOneSyscall ? kUdpBatchMax * rudp::kMaxDatagram : 0),
38
+ secret_rotated_at_(Clock::now()),
39
+ rng_(std::random_device{}()) {
40
+ set_socket_buffer_sizes(socket_, kSocketBufferBytes, kSocketBufferBytes);
41
+
42
+ // Point each slot at its slice of the storage, once. From here on a batch only
43
+ // ever moves lengths and endpoints around — the buffers never move.
44
+ for (size_t i = 0; i < kUdpBatchMax; ++i) {
45
+ recv_slots_[i].data = recv_storage_.data() + i * rudp::kMaxDatagram;
46
+ if (kUdpBatchIsOneSyscall)
47
+ send_slots_[i].data = send_storage_.data() + i * rudp::kMaxDatagram;
48
+ }
49
+
50
+ for (CookieSecret& secret : cookie_secret_) fill_random(secret.data(), secret.size());
51
+ }
52
+
53
+ UdpMux::~UdpMux() {
54
+ shutdown();
55
+ }
56
+
57
+ // ── Wire ────────────────────────────────────────────────────────────────────
58
+
59
+ void UdpMux::send_datagram(const Address& to, const uint8_t* data, size_t len) {
60
+ if (len == 0 || len > rudp::kMaxDatagram) return; // nothing this class produces
61
+
62
+ // Where the socket cannot take a batch in one call, staging buys nothing:
63
+ // flush_output() would make exactly the per-datagram calls this makes here, and
64
+ // the copy into the staging buffer on the way there would be pure loss — it
65
+ // would also undo the headroom trick in OutPacket, whose whole purpose is to
66
+ // hand the socket the packet where it already lies. So go straight to the wire.
67
+ if constexpr (!kUdpBatchIsOneSyscall) {
68
+ send_udp_to(socket_, data, len, to, family_);
69
+ return;
70
+ }
71
+
72
+ // Staged, not sent: a full congestion window is dozens of datagrams produced
73
+ // back to back, and handing each one to the kernel separately is where most of
74
+ // this transport's CPU used to go. They leave together in flush_output(), which
75
+ // every entry point calls before it returns — so nothing waits on a timer.
76
+ if (staged_ == kUdpBatchMax) flush_output();
77
+
78
+ UdpBatchSlot& slot = send_slots_[staged_++];
79
+ slot.endpoint = to;
80
+ slot.len = len;
81
+ std::memcpy(slot.data, data, len);
82
+ }
83
+
84
+ void UdpMux::flush_output() {
85
+ if (staged_ == 0) return;
86
+ const size_t staged = staged_;
87
+ staged_ = 0; // spent either way: what the socket refused is a dropped datagram
88
+
89
+ const size_t sent = send_udp_batch(socket_, send_slots_.data(), staged, family_);
90
+ if (sent < staged)
91
+ LOG_DEBUG("udp", "Socket took " << sent << "/" << staged
92
+ << " datagrams; the rest are dropped (retransmission covers them)");
93
+ }
94
+
95
+ void UdpMux::stream_events(UdpStream& stream, uint32_t events) {
96
+ // Recorded, never delivered from here: see the file comment. A stream with no
97
+ // connection (one that is lingering after its connection went away) has
98
+ // nowhere to send events, and quietly drops them.
99
+ const ConnId id = stream.conn_id();
100
+ if (id == kInvalidConnId) return;
101
+
102
+ // Coalesce. A stream raises PollIn for every packet it delivers, so a run of
103
+ // datagrams from one peer used to queue one dispatch per packet — and every one
104
+ // after the first found the connection's buffer already drained, having paid a
105
+ // map lookup and a read attempt to find that out.
106
+ //
107
+ // Scanning the tail rather than the whole list is what keeps this O(1) in the
108
+ // size of the node: during a receive batch the list cannot be longer than the
109
+ // batch, so the window covers all of it and the coalescing is exact; in tick(),
110
+ // where the list can hold an entry per stream, each stream is visited once and
111
+ // there is nothing to coalesce anyway.
112
+ const size_t from = pending_events_.size() > kUdpBatchMax
113
+ ? pending_events_.size() - kUdpBatchMax
114
+ : 0;
115
+ for (size_t i = from; i < pending_events_.size(); ++i) {
116
+ if (pending_events_[i].first != id) continue;
117
+ pending_events_[i].second |= events;
118
+ return;
119
+ }
120
+ pending_events_.emplace_back(id, events);
121
+ }
122
+
123
+ void UdpMux::send_reset(const Address& to, uint32_t conn_id) {
124
+ uint8_t buf[rudp::kHeaderSize];
125
+ rudp::Packet p;
126
+ p.type = rudp::PacketType::Reset;
127
+ p.conn_id = conn_id;
128
+ send_datagram(to, buf, rudp::encode(p, buf));
129
+ }
130
+
131
+ void UdpMux::send_retry(const Address& to, uint32_t conn_id, uint32_t cookie) {
132
+ uint8_t cookie_bytes[rudp::kCookieSize] = {
133
+ static_cast<uint8_t>(cookie >> 24), static_cast<uint8_t>(cookie >> 16),
134
+ static_cast<uint8_t>(cookie >> 8), static_cast<uint8_t>(cookie),
135
+ };
136
+
137
+ uint8_t buf[rudp::kHeaderSize + rudp::kCookieSize];
138
+ rudp::Packet p;
139
+ p.type = rudp::PacketType::Retry;
140
+ p.conn_id = conn_id;
141
+ p.payload = ByteView(cookie_bytes, sizeof(cookie_bytes));
142
+ send_datagram(to, buf, rudp::encode(p, buf));
143
+ }
144
+
145
+ bool UdpMux::spend_reply_budget(Clock::time_point now) {
146
+ // A window anchored to the clock, not a counter reset by tick(). The mux no
147
+ // longer sweeps on a fixed cadence, so "per tick" would mean "at whatever rate
148
+ // the streams happen to need servicing" — and on the node this budget actually
149
+ // protects, one with no streams at all being flooded with junk, that rate is
150
+ // zero. The rate itself is unchanged: 64 per 20 ms.
151
+ if (now - reply_window_started_ >= kUnsolicitedReplyWindow) {
152
+ reply_window_started_ = now;
153
+ replies_in_window_ = 0;
154
+ }
155
+ if (replies_in_window_ >= kMaxUnsolicitedReplies) return false;
156
+ ++replies_in_window_;
157
+ return true;
158
+ }
159
+
160
+ // ── Deadline scheduling ─────────────────────────────────────────────────────
161
+
162
+ void UdpMux::arm(uint32_t id, Entry& entry) {
163
+ std::optional<Clock::time_point> due = entry.stream->next_deadline();
164
+
165
+ if (entry.linger_until != Clock::time_point{}) {
166
+ // A released stream is also on a clock of its own. And once it has handed
167
+ // over everything it owed — or died trying — there is nothing left to wait
168
+ // for, so it wants collecting at the first opportunity rather than at the
169
+ // end of the linger. tick() runs once per reactor turn, so "the epoch"
170
+ // here means the very next turn.
171
+ if (entry.stream->flushed() || entry.stream->dead()) due = Clock::time_point{};
172
+ else if (!due) due = entry.linger_until;
173
+ else due = (std::min)(*due, entry.linger_until);
174
+ }
175
+
176
+ if (!due) return; // dead and still owned: release() will drop it, no timer needed
177
+
178
+ // Already covered. Note the asymmetry — a deadline that moved *earlier* must
179
+ // get a slot of its own, one that moved later needs nothing: the existing slot
180
+ // simply visits the stream early, finds nothing due, and re-arms from there.
181
+ if (entry.armed && *due >= entry.scheduled) return;
182
+
183
+ entry.scheduled = *due;
184
+ entry.armed = true;
185
+ due_.push_back(Due{*due, id});
186
+ std::push_heap(due_.begin(), due_.end(), LaterFirst{});
187
+ }
188
+
189
+ void UdpMux::compact_due() {
190
+ // Lazy deletion leaves a slot behind for every deadline that moved earlier, and
191
+ // for every stream that went away with a far-off deadline still scheduled — up
192
+ // to 45 s of idle timeout on a node with heavy connection churn. Each is
193
+ // dropped when it surfaces, so this only exists to stop them accumulating in
194
+ // between; the thresholds keep it off the path of anything but real churn.
195
+ if (due_.size() <= 64 || due_.size() <= 4 * streams_.size()) return;
196
+
197
+ std::vector<Due> live;
198
+ live.reserve(streams_.size());
199
+ for (const Due& slot : due_) {
200
+ const auto it = streams_.find(slot.id);
201
+ if (it != streams_.end() && it->second.armed && it->second.scheduled == slot.at)
202
+ live.push_back(slot);
203
+ }
204
+ due_.swap(live);
205
+ std::make_heap(due_.begin(), due_.end(), LaterFirst{});
206
+ }
207
+
208
+ int UdpMux::next_timeout_ms(int max_ms) const noexcept {
209
+ if (due_.empty()) return max_ms; // no stream wants waking: contribute nothing
210
+
211
+ const auto now = Clock::now();
212
+ const auto at = due_.front().at;
213
+ if (at <= now) return 0;
214
+ // Round up, exactly as TimerQueue does: truncating a sub-millisecond remainder
215
+ // to 0 would poll with a 0 ms timeout and burn CPU until the deadline instead
216
+ // of sleeping through it.
217
+ const auto ms = std::chrono::ceil<std::chrono::milliseconds>(at - now).count();
218
+ return static_cast<int>((std::min<long long>)(ms, max_ms));
219
+ }
220
+
221
+ void UdpMux::stream_touched(UdpStream& stream) {
222
+ const auto it = streams_.find(stream.recv_id());
223
+ if (it == streams_.end() || it->second.stream.get() != &stream) return;
224
+ arm(it->first, it->second);
225
+ }
226
+
227
+ // ── Address validation ──────────────────────────────────────────────────────
228
+
229
+ uint32_t UdpMux::cookie_for(const Address& from, uint32_t conn_id,
230
+ const CookieSecret& secret) const {
231
+ // A keyed hash of everything the cookie has to be tied to, truncated to the
232
+ // four bytes the wire carries. The secret goes in first: BLAKE2 is designed to
233
+ // be a MAC in exactly this prefix form (it has no length-extension weakness to
234
+ // work around), so there is no reason to reach for HMAC here.
235
+ //
236
+ // Binding the port as well as the address is what makes the cookie useless to
237
+ // anyone but the socket that asked for it, and binding conn_id keeps one cookie
238
+ // from opening a second stream. Nothing binds a timestamp: the secret's own
239
+ // rotation is what expires a cookie.
240
+ const ByteView ip = from.ip.bytes();
241
+ const uint8_t tail[6] = {
242
+ static_cast<uint8_t>(from.port >> 8), static_cast<uint8_t>(from.port),
243
+ static_cast<uint8_t>(conn_id >> 24), static_cast<uint8_t>(conn_id >> 16),
244
+ static_cast<uint8_t>(conn_id >> 8), static_cast<uint8_t>(conn_id),
245
+ };
246
+
247
+ rats_blake2s_context_t ctx;
248
+ rats_blake2s_reset(&ctx);
249
+ rats_blake2s_update(&ctx, secret.data(), secret.size());
250
+ rats_blake2s_update(&ctx, ip.data(), ip.size());
251
+ rats_blake2s_update(&ctx, tail, sizeof(tail));
252
+
253
+ uint8_t hash[RATS_BLAKE2S_HASH_SIZE];
254
+ rats_blake2s_finish(&ctx, hash);
255
+ return (static_cast<uint32_t>(hash[0]) << 24) | (static_cast<uint32_t>(hash[1]) << 16) |
256
+ (static_cast<uint32_t>(hash[2]) << 8) | static_cast<uint32_t>(hash[3]);
257
+ }
258
+
259
+ bool UdpMux::cookie_valid(const Address& from, uint32_t conn_id, ByteView payload) const {
260
+ if (payload.size() != rudp::kCookieSize) return false;
261
+
262
+ const uint8_t* bytes = payload.data();
263
+ const uint32_t offered = (static_cast<uint32_t>(bytes[0]) << 24) |
264
+ (static_cast<uint32_t>(bytes[1]) << 16) |
265
+ (static_cast<uint32_t>(bytes[2]) << 8) |
266
+ static_cast<uint32_t>(bytes[3]);
267
+
268
+ // Both secrets, so a cookie handed out just before a rotation is still good.
269
+ for (const CookieSecret& secret : cookie_secret_)
270
+ if (cookie_for(from, conn_id, secret) == offered) return true;
271
+ return false;
272
+ }
273
+
274
+ void UdpMux::rotate_cookie_secret(Clock::time_point now) {
275
+ if (now - secret_rotated_at_ < kCookieSecretLifetime) return;
276
+ cookie_secret_[1] = cookie_secret_[0];
277
+ fill_random(cookie_secret_[0].data(), cookie_secret_[0].size());
278
+ secret_rotated_at_ = now;
279
+ }
280
+
281
+ // ── Inbound datagrams ───────────────────────────────────────────────────────
282
+
283
+ bool UdpMux::on_readable() {
284
+ const auto now = Clock::now();
285
+ // Cookies are minted from here (a Retry answering an unproven Syn), so this is
286
+ // where the secret has to be current. tick() rotates it too, but a node with no
287
+ // streams has no deadlines and therefore no ticks — and that is exactly the
288
+ // node a flood of Syns arrives at.
289
+ rotate_cookie_secret(now);
290
+
291
+ size_t drained = 0;
292
+ bool more = false;
293
+ for (;;) {
294
+ if (drained >= kMaxDatagramsPerRead) { more = true; break; }
295
+
296
+ // Never ask for more than the cap allows, so the bound on how long one
297
+ // flood can hold the loop stays exact rather than approximate.
298
+ const size_t want = (std::min)(kUdpBatchMax, kMaxDatagramsPerRead - drained);
299
+ for (size_t i = 0; i < want; ++i) recv_slots_[i].len = rudp::kMaxDatagram;
300
+
301
+ const std::ptrdiff_t got = recv_udp_batch(socket_, recv_slots_.data(), want);
302
+ if (got == kUdpRecvWouldBlock) break;
303
+ // A per-destination error (an ICMP unreachable for an earlier datagram,
304
+ // which Windows in particular reports on the *next* receive) says nothing
305
+ // about the socket, so keep draining rather than abandoning every other
306
+ // peer on it. The loop is bounded, so a persistent error cannot spin.
307
+ if (got == kUdpRecvError) { ++drained; continue; }
308
+
309
+ for (std::ptrdiff_t i = 0; i < got; ++i) {
310
+ const UdpBatchSlot& slot = recv_slots_[static_cast<size_t>(i)];
311
+ rudp::Packet p;
312
+ if (!rudp::decode(slot.data, slot.len, p)) continue;
313
+ handle_datagram(p, slot.endpoint, now);
314
+ }
315
+ drained += static_cast<size_t>(got);
316
+
317
+ // Hand this batch's events over before reading the next one, rather than
318
+ // saving them all for the end of the drain. What a stream delivers sits in
319
+ // its in-order buffer until its connection reads it out, so deferring the
320
+ // whole drain let that buffer grow to everything one peer managed to send
321
+ // in it — a megabyte and more, written and then read back through main
322
+ // memory, and enough to close the receive window mid-drain. Draining per
323
+ // batch keeps it to one batch's worth, which stays in cache.
324
+ //
325
+ // Safe here and nowhere earlier: the slot loop above has finished, so no
326
+ // reference into streams_ is live, and a handler that closes its connection
327
+ // only marks it — the teardown that would free a stream runs later, on the
328
+ // reactor.
329
+ dispatch_pending();
330
+ // A short batch usually means the queue is empty, but not always (a
331
+ // per-datagram error truncates one too), and the kqueue backend is
332
+ // edge-triggered — so keep going until the socket says would-block.
333
+ }
334
+
335
+ dispatch_pending();
336
+ flush_output(); // the acks and repairs the batch above produced leave together
337
+ return more;
338
+ }
339
+
340
+ void UdpMux::handle_datagram(const rudp::Packet& p, const Address& from, Clock::time_point now) {
341
+ auto it = streams_.find(p.conn_id);
342
+ if (it != streams_.end()) {
343
+ UdpStream& stream = *it->second.stream;
344
+ // The connection id is the whole authorisation to touch this stream, and a
345
+ // datagram source address is trivially forged. Pinning the stream to the
346
+ // address it was established with means an attacker would have to guess a
347
+ // random 32-bit id *and* be on the path. (It also means this transport does
348
+ // not follow a peer across a NAT rebind — the peer redials, which the
349
+ // handshake makes cheap and safe.)
350
+ if (stream.remote() != from) return;
351
+ stream.on_packet(p, now);
352
+ arm(it->first, it->second); // the packet may have brought a deadline forward
353
+ return;
354
+ }
355
+
356
+ if (p.type == rudp::PacketType::Syn) {
357
+ if (admit_syn(p, from, now)) accept_inbound(p, from, now);
358
+ return;
359
+ }
360
+
361
+ if (p.type == rudp::PacketType::Reset) {
362
+ // A reset for a stream the sender never knew carries the id *it* was
363
+ // given, which is our send id rather than our recv id — see find_for_reset.
364
+ if (UdpStream* stream = find_for_reset(p.conn_id, from)) {
365
+ stream->on_packet(p, now);
366
+ stream_touched(*stream); // now dead, or a lingering one worth collecting
367
+ }
368
+ return;
369
+ }
370
+
371
+ // Nothing here answers to that id: a stream we closed, or one left over from a
372
+ // previous run of this node. Say so, so the peer gives up now instead of
373
+ // retransmitting for a minute — but never more than a bounded rate, so a flood
374
+ // of garbage cannot turn this socket into a reflector.
375
+ if (spend_reply_budget(now)) send_reset(from, p.conn_id);
376
+ }
377
+
378
+ bool UdpMux::admit_syn(const rudp::Packet& syn, const Address& from, Clock::time_point now) {
379
+ // Everything this function refuses is refused BEFORE a stream exists. That is
380
+ // the whole point: a Syn is sixteen bytes from an address nobody has verified,
381
+ // and answering it with a stream, a connection and a half-built Noise handshake
382
+ // is a trade an attacker with a forged source address would happily make a
383
+ // million times a second. TCP is spared this by its three-way handshake and by
384
+ // the file descriptor an accepted connection costs; here the two checks below
385
+ // stand in for both.
386
+ //
387
+ // The id the dialer listens on is one below the one it sent under, so that is
388
+ // where any answer has to go — the same pairing accept_inbound() relies on.
389
+ const uint32_t reply_id = syn.conn_id - 1;
390
+
391
+ if (streams_.size() >= limits_.max_streams) {
392
+ LOG_DEBUG("udp", "Refusing a dial from " << from.to_string() << ": at the stream ceiling ("
393
+ << limits_.max_streams << ")");
394
+ if (spend_reply_budget(now)) send_reset(from, reply_id);
395
+ return false;
396
+ }
397
+
398
+ if (streams_.size() < limits_.validate_above) return true; // not under pressure
399
+
400
+ if (cookie_valid(from, syn.conn_id, syn.payload)) return true;
401
+
402
+ // Unproven. Hand back a cookie and keep nothing: if the address was forged, the
403
+ // cookie goes to whoever really lives there and this dial simply never happens.
404
+ if (spend_reply_budget(now))
405
+ send_retry(from, reply_id, cookie_for(from, syn.conn_id, cookie_secret_[0]));
406
+ return false;
407
+ }
408
+
409
+ void UdpMux::accept_inbound(const rudp::Packet& syn, const Address& from, Clock::time_point now) {
410
+ // Id pairing: the dialer picked `base`, keeps `base` as its recv id and sends
411
+ // under `base + 1`. So a Syn's conn_id is our recv id, and one less is what we
412
+ // must send under to reach the dialer. One random number, two ids, no
413
+ // negotiation round trip.
414
+ const uint32_t recv_id = syn.conn_id;
415
+ const uint32_t send_id = syn.conn_id - 1;
416
+
417
+ auto stream = std::make_unique<UdpStream>(*this, from, recv_id, send_id,
418
+ ConnRole::Inbound, now);
419
+ UdpStream* raw = stream.get();
420
+ streams_.emplace(recv_id, Entry{std::move(stream)});
421
+
422
+ // Adoption takes the link; refusing it destroys the link, which releases the
423
+ // stream — so `raw` must not be touched again on that path.
424
+ const ConnId id = delegate_.adopt_inbound_link(std::make_unique<UdpStreamLink>(*this, *raw));
425
+ if (id == kInvalidConnId) {
426
+ send_reset(from, send_id);
427
+ return;
428
+ }
429
+
430
+ LOG_DEBUG("udp", "Accepted stream " << recv_id << " from " << from.to_string());
431
+ raw->on_packet(syn, now); // acknowledges the Syn and opens the stream
432
+ stream_touched(*raw); // first deadline: the idle timer, at the very least
433
+ }
434
+
435
+ UdpStream* UdpMux::find_for_reset(uint32_t conn_id, const Address& from) {
436
+ // A reset generated for an unknown stream can only echo the id that was in the
437
+ // datagram that provoked it — which is our *send* id, not the recv id the map
438
+ // is keyed by. The two always differ by exactly one (see accept_inbound), so
439
+ // two probes find it, and both are validated against the send id and the peer
440
+ // address before anything is acted on.
441
+ for (const uint32_t candidate : {conn_id - 1, conn_id + 1}) {
442
+ auto it = streams_.find(candidate);
443
+ if (it == streams_.end()) continue;
444
+ UdpStream& stream = *it->second.stream;
445
+ if (stream.send_id() == conn_id && stream.remote() == from) return &stream;
446
+ }
447
+ return nullptr;
448
+ }
449
+
450
+ // ── Outbound ────────────────────────────────────────────────────────────────
451
+
452
+ bool UdpMux::allocate_ids(uint32_t& recv_id, uint32_t& send_id) {
453
+ std::uniform_int_distribution<uint32_t> pick(1, 0xFFFFFFFEu); // keeps base+1 non-zero
454
+ for (int attempt = 0; attempt < 32; ++attempt) {
455
+ const uint32_t base = pick(rng_);
456
+ if (streams_.count(base) || streams_.count(base + 1)) continue;
457
+ recv_id = base;
458
+ send_id = base + 1;
459
+ return true;
460
+ }
461
+ return false; // 32 collisions in a 4-billion space: the map is implausibly full
462
+ }
463
+
464
+ std::unique_ptr<Link> UdpMux::connect(const Address& remote, DialProfile profile) {
465
+ uint32_t recv_id = 0, send_id = 0;
466
+ if (!allocate_ids(recv_id, send_id)) {
467
+ LOG_WARN("udp", "Could not allocate a connection id for " << remote.to_string());
468
+ return nullptr;
469
+ }
470
+
471
+ auto stream = std::make_unique<UdpStream>(*this, remote, recv_id, send_id,
472
+ ConnRole::Outbound, Clock::now(), profile);
473
+ UdpStream* raw = stream.get();
474
+ const auto inserted = streams_.emplace(recv_id, Entry{std::move(stream)});
475
+ arm(recv_id, inserted.first->second); // the Syn's retransmission timeout
476
+ flush_output(); // the Syn is the dial; it does not wait for company
477
+ return std::make_unique<UdpStreamLink>(*this, *raw);
478
+ }
479
+
480
+ // ── Lifecycle ───────────────────────────────────────────────────────────────
481
+
482
+ void UdpMux::release(UdpStream& stream) {
483
+ auto it = streams_.find(stream.recv_id());
484
+ if (it == streams_.end() || it->second.stream.get() != &stream) return;
485
+
486
+ stream.set_conn_id(kInvalidConnId); // events have nowhere to go from here on
487
+
488
+ if (stream.connecting()) {
489
+ // A dial that was called off (the other transport won the race, or the
490
+ // node is going down). There is nothing to hand over, and a lingering Syn
491
+ // would keep asking a peer to open a stream nobody will read.
492
+ stream.abort(Clock::now());
493
+ streams_.erase(it);
494
+ } else if (stream.dead() || stream.flushed()) {
495
+ streams_.erase(it);
496
+ } else {
497
+ // Something is still owed to the peer. Keep the stream just long enough to
498
+ // hand it over — this is the user-space equivalent of the kernel continuing
499
+ // to retransmit after close(), and without it a node that sends a final
500
+ // message and disconnects would drop it on the floor.
501
+ it->second.linger_until = Clock::now() + kLingerTimeout;
502
+ arm(it->first, it->second); // the linger is a deadline like any other
503
+ }
504
+
505
+ // The Connection told the link goodbye just before letting go of it, so a Fin
506
+ // or a Reset is usually staged by the time we get here. It must not outlive the
507
+ // call that produced it.
508
+ flush_output();
509
+ }
510
+
511
+ void UdpMux::tick() {
512
+ // The whole point of the deadline heap: with nothing scheduled, or nothing yet
513
+ // due, this is one comparison — which is what makes it safe for the reactor to
514
+ // call on every turn of its loop instead of arming a timer for it.
515
+ if (due_.empty()) return;
516
+ const auto now = Clock::now();
517
+ if (due_.front().at > now) return;
518
+
519
+ rotate_cookie_secret(now);
520
+
521
+ // Only what has come due. Bounded by the slots that were already scheduled when
522
+ // this call began: servicing a stream re-arms it, and a re-arm that lands on
523
+ // "now" (a lingering stream that just flushed) would otherwise be picked up by
524
+ // this same loop. Deferring those to the next turn — which the reactor takes
525
+ // immediately, since next_timeout_ms() then returns 0 — keeps the loop finite
526
+ // by construction rather than by argument.
527
+ size_t budget = due_.size();
528
+ while (budget-- > 0 && !due_.empty() && due_.front().at <= now) {
529
+ const Due slot = due_.front();
530
+ std::pop_heap(due_.begin(), due_.end(), LaterFirst{});
531
+ due_.pop_back();
532
+
533
+ const auto it = streams_.find(slot.id);
534
+ if (it == streams_.end()) continue; // the stream went away; the slot is litter
535
+ Entry& entry = it->second;
536
+ // Superseded: a later arm() gave this stream an earlier slot, which has
537
+ // already been (or is about to be) serviced. Lazy deletion, so arm() never
538
+ // has to find and remove anything.
539
+ if (!entry.armed || entry.scheduled != slot.at) continue;
540
+ entry.armed = false;
541
+
542
+ UdpStream& stream = *entry.stream;
543
+ stream.tick(now);
544
+
545
+ const bool lingering = entry.linger_until != Clock::time_point{};
546
+ if (lingering && (stream.flushed() || stream.dead() || now >= entry.linger_until)) {
547
+ streams_.erase(it);
548
+ continue;
549
+ }
550
+ arm(slot.id, entry);
551
+ }
552
+
553
+ compact_due();
554
+ dispatch_pending();
555
+ flush_output(); // one syscall for every stream's retransmissions and keep-alives
556
+ }
557
+
558
+ void UdpMux::shutdown() {
559
+ const auto now = Clock::now();
560
+ for (auto& entry : streams_) entry.second.stream->abort(now);
561
+ flush_output(); // the goodbyes go out before the streams they came from
562
+ streams_.clear();
563
+ due_.clear();
564
+ pending_events_.clear();
565
+ }
566
+
567
+ void UdpMux::dispatch_pending() {
568
+ if (pending_events_.empty()) return;
569
+ // Swap the batch out first: a handler may close its connection, which releases
570
+ // a stream and can record further events. Those are picked up by the next
571
+ // drain or tick rather than extending the loop we are inside.
572
+ auto batch = std::move(pending_events_);
573
+ pending_events_.clear();
574
+ for (const auto& [id, events] : batch) delegate_.dispatch_link_events(id, events);
575
+ }
576
+
577
+ // ── UdpStreamLink ───────────────────────────────────────────────────────────
578
+
579
+ Link::IoResult UdpStreamLink::read(ByteSpan into) {
580
+ const size_t n = stream_.read(into.data(), into.size());
581
+ // Draining the in-order buffer can re-open a receive window we had advertised
582
+ // as full, which the stream owes the peer an acknowledgement for *at once* —
583
+ // the earliest deadline there is. Re-arming here is what stops that ack from
584
+ // waiting on some unrelated timer.
585
+ if (n > 0) mux_.stream_touched(stream_);
586
+
587
+ if (n > 0) return {n, Status::Ok};
588
+ if (stream_.eof()) return {0, Status::Closed};
589
+ if (stream_.dead()) return {0, Status::Error};
590
+ return {0, Status::WouldBlock};
591
+ }
592
+
593
+ Link::IoResult UdpStreamLink::write(const ByteView* slices, size_t count) {
594
+ if (stream_.dead()) return {0, Status::Error};
595
+ const size_t n = stream_.write(slices, count, UdpMux::Clock::now());
596
+ // Whatever went out started the retransmission timer. On a stream that was idle
597
+ // a moment ago that is a deadline hundreds of milliseconds out, against a
598
+ // keep-alive ten seconds out — so without this the first loss on a quiet stream
599
+ // would not be repaired until the keep-alive happened to wake it.
600
+ if (n > 0) mux_.stream_touched(stream_);
601
+ return {n, n > 0 ? Status::Ok : Status::WouldBlock};
602
+ }
603
+
604
+ void UdpStreamLink::close(CloseReason reason) {
605
+ const auto now = UdpMux::Clock::now();
606
+ // An application-level close deserves an orderly one: queue a Fin behind
607
+ // whatever is still unsent, so the peer receives every byte and then a clean
608
+ // end of stream. Anything else — a protocol error, a duplicate connection, the
609
+ // node going down — has nothing worth flushing, so say so at once.
610
+ if (reason == CloseReason::LocalClose || reason == CloseReason::PeerClosed)
611
+ stream_.begin_close(now);
612
+ else
613
+ stream_.abort(now);
614
+ mux_.stream_touched(stream_);
615
+ }
616
+
617
+ } // namespace librats