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