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,413 @@
1
+ #include "librats/bittorrent/client.h"
2
+ #include "librats/bittorrent/log.h"
3
+
4
+ #include <algorithm>
5
+ #include <chrono>
6
+ #include <mutex>
7
+ #include <utility>
8
+
9
+ namespace librats::bittorrent {
10
+
11
+ Client::Client() : Client(Config{}) {}
12
+
13
+ Client::Client(Config config)
14
+ : config_(std::move(config))
15
+ , peer_id_(generate_peer_id(config_.peer_id_prefix)) {}
16
+
17
+ Client::~Client() {
18
+ stop();
19
+ }
20
+
21
+ void Client::open() {
22
+ if (opened_) return;
23
+ opened_ = true;
24
+ open_listener();
25
+ schedule_reap();
26
+ }
27
+
28
+ void Client::start() {
29
+ open();
30
+ reactor_.start();
31
+ }
32
+
33
+ void Client::stop() {
34
+ if (!opened_) return;
35
+ // Disarm the DHT callbacks BEFORE the reactor goes away: the shared DHT keeps
36
+ // running after us (node teardown is reverse-attach order) and a get_peers reply
37
+ // for a torrent we are about to drop would otherwise post to a destroyed reactor.
38
+ {
39
+ std::lock_guard<std::mutex> lock(dht_guard_->mutex);
40
+ dht_guard_->alive = false;
41
+ }
42
+ reactor_.stop(); // join the loop thread first so nothing touches state concurrently
43
+ opened_ = false;
44
+ if (reap_timer_ != kInvalidTimerId) { reactor_.cancel(reap_timer_); reap_timer_ = kInvalidTimerId; }
45
+ for (auto& [hash, t] : torrents_) {
46
+ // Drop the traversal too: nothing consumes its result any more, and it would
47
+ // keep hammering the DHT for a torrent that no longer exists.
48
+ if (dht_) dht_->cancel_search(hash);
49
+ t->stop();
50
+ }
51
+ torrents_.clear();
52
+ connections_.clear();
53
+ // Reclaim any outbound sockets still mid-connect (their completion lambda will
54
+ // never run now that the reactor is stopped).
55
+ for (socket_t s : pending_connects_) { reactor_.remove(s); close_socket(s); }
56
+ pending_connects_.clear();
57
+ if (is_valid_socket(listener_)) { reactor_.remove(listener_); close_socket(listener_); listener_ = RATS_INVALID_SOCKET; }
58
+ // Fresh token, so a Client that is start()ed again gets DHT peers delivered.
59
+ // Safe to swap unsynchronised: the reactor thread is joined and the old token is
60
+ // kept alive by whatever callbacks still hold it.
61
+ dht_guard_ = std::make_shared<DhtCallbackGuard>();
62
+ }
63
+
64
+ void Client::open_listener() {
65
+ listener_ = create_tcp_server(config_.listen_port, 16, "", AddressFamily::IPv4);
66
+ if (!is_valid_socket(listener_)) {
67
+ LOG_ERROR("bt.client", "failed to bind listen port " << config_.listen_port
68
+ << " — inbound peers disabled");
69
+ return;
70
+ }
71
+ set_socket_nonblocking(listener_);
72
+ actual_port_ = std::uint16_t(get_bound_port(listener_));
73
+ reactor_.add(listener_, PollIn, [this](std::uint32_t) { on_accept(); });
74
+ LOG_INFO("bt.client", "listening on port " << actual_port_);
75
+ }
76
+
77
+ void Client::on_accept() {
78
+ for (;;) {
79
+ // Raw accept so a non-blocking EWOULDBLOCK drain is quiet (accept_client
80
+ // logs it as an error). nullptr addr: we read the peer address elsewhere.
81
+ socket_t s = ::accept(listener_, nullptr, nullptr);
82
+ if (!is_valid_socket(s)) break; // drained (non-blocking listener)
83
+ suppress_sigpipe(s); // not inherited from the listener; raw accept()
84
+ // Cap total connections so an inbound flood can't exhaust memory / fds (H3).
85
+ // Keep draining the accept queue, but immediately drop anything over the cap.
86
+ if (connections_.size() >= kMaxConnections) {
87
+ LOG_DEBUG("bt.client", "connection cap " << kMaxConnections << " reached, dropping inbound");
88
+ close_socket(s);
89
+ continue;
90
+ }
91
+ set_socket_nonblocking(s);
92
+
93
+ // Remote source endpoint (for logging / PEX). The peer's *listen* port is
94
+ // learned later from its extended handshake; this is the ephemeral source.
95
+ std::string ip;
96
+ std::uint16_t port = 0;
97
+ if (const std::string ep = get_peer_address(s); !ep.empty()) {
98
+ const std::size_t colon = ep.rfind(':');
99
+ if (colon != std::string::npos) {
100
+ ip = ep.substr(0, colon);
101
+ port = std::uint16_t(std::atoi(ep.c_str() + colon + 1));
102
+ }
103
+ }
104
+
105
+ auto resolver = [this](const InfoHash& ih, PeerConnection::Binding& out) -> bool {
106
+ auto it = torrents_.find(ih);
107
+ if (it == torrents_.end() || !it->second) return false;
108
+ out.observer = it->second.get();
109
+ out.num_pieces = it->second->num_pieces();
110
+ return true;
111
+ };
112
+ auto pc = std::make_unique<PeerConnection>(reactor_, s, peer_id_, std::move(resolver),
113
+ std::move(ip), port);
114
+ LOG_DEBUG("bt.client", "inbound connection from " << ip << ':' << port);
115
+ PeerConnection* raw = pc.get();
116
+ connections_.push_back(std::move(pc));
117
+ raw->start();
118
+ }
119
+ }
120
+
121
+ void Client::connect_peer(Torrent& torrent, const std::string& ip, std::uint16_t port) {
122
+ if (connections_.size() >= kMaxConnections) { torrent.on_connect_failed(ip, port); return; }
123
+ socket_t s = tcp_connect_start(ip, int(port));
124
+ if (!is_valid_socket(s)) { torrent.on_connect_failed(ip, port); return; }
125
+
126
+ // Capture the info-hash, not a raw Torrent*: the torrent may be removed before
127
+ // the connect completes, so we re-resolve it (and bail if it's gone) rather
128
+ // than dereference a dangling pointer (H10). The socket is tracked so a
129
+ // mid-connect stop() can reclaim it.
130
+ const InfoHash ih = torrent.info_hash();
131
+ pending_connects_.insert(s);
132
+ reactor_.add(s, PollOut, [this, ih, s, ip, port](std::uint32_t) {
133
+ reactor_.remove(s); // done watching for connect completion
134
+ pending_connects_.erase(s);
135
+ auto it = torrents_.find(ih);
136
+ Torrent* t = (it != torrents_.end()) ? it->second.get() : nullptr;
137
+ if (tcp_connect_result(s) != 0 || !t) {
138
+ close_socket(s);
139
+ if (t) t->on_connect_failed(ip, port);
140
+ return;
141
+ }
142
+ auto pc = std::make_unique<PeerConnection>(reactor_, s, /*outgoing=*/true,
143
+ t->info_hash(), peer_id_, t->num_pieces(), t,
144
+ ip, port);
145
+ PeerConnection* raw = pc.get();
146
+ connections_.push_back(std::move(pc));
147
+ raw->start();
148
+ });
149
+ }
150
+
151
+ void Client::find_peers_via_dht(const InfoHash& info_hash,
152
+ std::function<void(const std::string&, std::uint16_t)> on_peer) {
153
+ if (!dht_ || !dht_->is_running()) return;
154
+ // DhtClient delivers results on its own thread; marshal them onto the reactor.
155
+ // Re-resolve the torrent by info-hash before invoking on_peer: the torrent may
156
+ // have been removed between the get_peers request and its (seconds-later) reply,
157
+ // and on_peer captures the Torrent by pointer — dereferencing it after removal is
158
+ // a use-after-free of peer_list_ (same H10 hazard fixed in connect_peer). Removal
159
+ // happens only on the reactor thread, so a torrent present here stays alive for
160
+ // the whole callback.
161
+ // The guard covers the *other* half of that hazard: the whole Client may be gone
162
+ // by the time the reply lands, since the shared DHT is torn down after us. Held
163
+ // for the post() only — the work itself still runs on the reactor.
164
+ dht_->find_peers(info_hash, [this, guard = dht_guard_, info_hash, on_peer](const std::vector<Address>& peers,
165
+ const InfoHash&) {
166
+ std::lock_guard<std::mutex> lock(guard->mutex);
167
+ if (!guard->alive) return; // Client stopped/destroyed — its reactor is gone
168
+ reactor_.post([this, info_hash, peers, on_peer] {
169
+ if (torrents_.find(info_hash) == torrents_.end()) return; // torrent gone
170
+ for (const Address& a : peers) on_peer(a.ip.to_string(), a.port);
171
+ });
172
+ });
173
+ }
174
+
175
+ void Client::announce_to_dht(const InfoHash& info_hash, std::uint16_t port) {
176
+ // Publish ourselves to the info-hash's DHT nodes so other clients' get_peers
177
+ // find us (BEP 5). DhtClient is the node's shared, thread-safe instance.
178
+ if (dht_ && dht_->is_running()) dht_->announce_peer(info_hash, port);
179
+ }
180
+
181
+ Torrent* Client::add_torrent(const TorrentInfo& info, const std::string& save_path) {
182
+ return run_on_reactor([&] { return add_torrent_impl(info, save_path); });
183
+ }
184
+
185
+ Torrent* Client::add_torrent_impl(const TorrentInfo& info, const std::string& save_path) {
186
+ if (!info.is_valid() || !info.has_metadata()) {
187
+ LOG_WARN("bt.client", "rejected invalid/incomplete torrent");
188
+ return nullptr;
189
+ }
190
+ const InfoHash ih = info.info_hash();
191
+ if (torrents_.count(ih)) return torrents_[ih].get();
192
+
193
+ const std::string path = save_path.empty() ? config_.download_path : save_path;
194
+ auto t = std::make_unique<Torrent>(reactor_, *this, info, path);
195
+ Torrent* raw = t.get();
196
+ torrents_.emplace(ih, std::move(t));
197
+ LOG_INFO("bt.client", "added torrent " << short_hash(ih) << " \"" << info.name() << "\" → " << path);
198
+ raw->start();
199
+ return raw;
200
+ }
201
+
202
+ Torrent* Client::add_magnet(const std::string& magnet_uri, const std::string& save_path) {
203
+ return run_on_reactor([&] { return add_magnet_impl(magnet_uri, save_path, /*resume=*/false); });
204
+ }
205
+
206
+ Torrent* Client::add_magnet_resumed(const std::string& magnet_uri, const std::string& save_path) {
207
+ return run_on_reactor([&] { return add_magnet_impl(magnet_uri, save_path, /*resume=*/true); });
208
+ }
209
+
210
+ Torrent* Client::add_magnet_impl(const std::string& magnet_uri, const std::string& save_path, bool resume) {
211
+ auto info = TorrentInfo::from_magnet(magnet_uri);
212
+ if (!info || !info->is_valid()) {
213
+ LOG_WARN("bt.client", "rejected invalid magnet uri");
214
+ return nullptr;
215
+ }
216
+ const InfoHash ih = info->info_hash();
217
+ if (torrents_.count(ih)) return torrents_[ih].get();
218
+
219
+ const std::string path = save_path.empty() ? config_.download_path : save_path;
220
+ auto t = std::make_unique<Torrent>(reactor_, *this, *info, path);
221
+ Torrent* raw = t.get();
222
+ torrents_.emplace(ih, std::move(t));
223
+ // Resume must be applied before start(); it completes the metadata + trusted have
224
+ // set if a resume file exists next to the download.
225
+ if (resume && raw->try_load_resume_data())
226
+ LOG_INFO("bt.client", "restored resume data for " << short_hash(ih));
227
+ LOG_INFO("bt.client", "added magnet" << (resume ? " (resumed) " : " ") << short_hash(ih) << " → " << path);
228
+ raw->start();
229
+ return raw;
230
+ }
231
+
232
+ Torrent* Client::add_torrent_with_resume(const TorrentInfo& info, const ResumeData& resume,
233
+ const std::string& save_path) {
234
+ return run_on_reactor([&] { return add_torrent_with_resume_impl(info, resume, save_path); });
235
+ }
236
+
237
+ Torrent* Client::add_torrent_with_resume_impl(const TorrentInfo& info, const ResumeData& resume,
238
+ const std::string& save_path) {
239
+ if (!info.is_valid()) return nullptr;
240
+ const InfoHash ih = info.info_hash();
241
+ if (torrents_.count(ih)) return torrents_[ih].get();
242
+
243
+ const std::string path = save_path.empty() ? config_.download_path : save_path;
244
+ auto t = std::make_unique<Torrent>(reactor_, *this, info, path);
245
+ Torrent* raw = t.get();
246
+ torrents_.emplace(ih, std::move(t));
247
+ raw->load_resume_data(resume); // must precede start()
248
+ raw->start();
249
+ return raw;
250
+ }
251
+
252
+ Torrent* Client::add_torrent_for_seeding(const TorrentInfo& info, const std::string& save_path) {
253
+ return run_on_reactor([&]() -> Torrent* {
254
+ if (!info.is_valid() || !info.has_metadata()) return nullptr;
255
+ ResumeData rd;
256
+ rd.info_hash = info.info_hash();
257
+ rd.have = Bitfield(info.num_pieces(), true); // assume every piece is present
258
+ return add_torrent_with_resume_impl(info, rd, save_path);
259
+ });
260
+ }
261
+
262
+ void Client::save_all_resume_data() {
263
+ run_on_reactor([&] { for (auto& [hash, t] : torrents_) t->save_resume_data(); });
264
+ }
265
+
266
+ Torrent* Client::get_torrent(const InfoHash& info_hash) {
267
+ auto it = torrents_.find(info_hash);
268
+ return it == torrents_.end() ? nullptr : it->second.get();
269
+ }
270
+
271
+ void Client::remove_torrent(const InfoHash& info_hash, bool /*delete_files*/) {
272
+ run_on_reactor([&] { remove_torrent_impl(info_hash); });
273
+ }
274
+
275
+ void Client::remove_torrent_impl(const InfoHash& info_hash) {
276
+ auto it = torrents_.find(info_hash);
277
+ if (it == torrents_.end()) return;
278
+ LOG_INFO("bt.client", "removed torrent " << short_hash(info_hash));
279
+ it->second->stop(); // closes the peers the torrent has registered in its peer list
280
+ // stop() only closes connections the torrent knows about (its peers_ list). An
281
+ // *outgoing* connection that has completed connect() but not yet the wire
282
+ // handshake holds obs_ = this torrent, yet is not in peers_ until on_handshake —
283
+ // so stop() misses it. Erasing the torrent now would leave that live connection
284
+ // with a dangling observer, and its next message (handshake reply + payload in
285
+ // one segment) dereferences the freed Torrent → use-after-free crashing in
286
+ // dispatch(). Close every remaining live connection bound to this info-hash
287
+ // while the torrent is still alive (close() fires on_closed() on it).
288
+ for (auto& pc : connections_)
289
+ if (!pc->closed() && pc->info_hash() == info_hash) pc->close("torrent removed");
290
+ torrents_.erase(it);
291
+ // Stop the DHT traversal started for this torrent: its result has no consumer
292
+ // left, and letting it run keeps a lookup alive for a hash we no longer hold.
293
+ if (dht_) dht_->cancel_search(info_hash);
294
+ // File deletion is not yet implemented; the torrent's data is left on disk.
295
+ }
296
+
297
+ TorrentStatus Client::torrent_status(const InfoHash& info_hash) {
298
+ return run_on_reactor([&]() -> TorrentStatus {
299
+ TorrentStatus s;
300
+ auto it = torrents_.find(info_hash);
301
+ if (it == torrents_.end() || !it->second) return s;
302
+ const Torrent* t = it->second.get();
303
+ const TorrentInfo& info = t->torrent_info();
304
+ s.exists = true;
305
+ s.name = info.name();
306
+ s.has_metadata = t->has_metadata();
307
+ s.is_complete = t->is_complete();
308
+ s.paused = t->is_paused();
309
+ s.progress = t->progress();
310
+ s.downloaded = t->bytes_downloaded();
311
+ s.uploaded = t->bytes_uploaded();
312
+ s.num_peers = t->num_peers();
313
+ if (info.has_metadata()) {
314
+ s.total_size = std::uint64_t(info.total_size());
315
+ for (const FileEntry& f : info.files().files())
316
+ s.files.push_back({f.path, f.size});
317
+ }
318
+ return s;
319
+ });
320
+ }
321
+
322
+ std::optional<TorrentInfo> Client::torrent_metadata(const InfoHash& info_hash) {
323
+ return run_on_reactor([&]() -> std::optional<TorrentInfo> {
324
+ auto it = torrents_.find(info_hash);
325
+ if (it == torrents_.end() || !it->second) return std::nullopt;
326
+ const Torrent* t = it->second.get();
327
+ if (!t->has_metadata()) return std::nullopt;
328
+ // Copy the info out under reactor ownership; TorrentInfo is a value type,
329
+ // so the caller gets a standalone snapshot safe to use off-thread.
330
+ return t->torrent_info();
331
+ });
332
+ }
333
+
334
+ void Client::pause_torrent(const InfoHash& info_hash) {
335
+ run_on_reactor([&] {
336
+ auto it = torrents_.find(info_hash);
337
+ if (it != torrents_.end() && it->second) it->second->pause();
338
+ });
339
+ }
340
+
341
+ void Client::resume_torrent(const InfoHash& info_hash) {
342
+ run_on_reactor([&] {
343
+ auto it = torrents_.find(info_hash);
344
+ if (it != torrents_.end() && it->second) it->second->resume();
345
+ });
346
+ }
347
+
348
+ bool Client::save_resume_data(const InfoHash& info_hash) {
349
+ return run_on_reactor([&]() -> bool {
350
+ auto it = torrents_.find(info_hash);
351
+ return it != torrents_.end() && it->second && it->second->save_resume_data();
352
+ });
353
+ }
354
+
355
+ std::vector<Torrent*> Client::torrents() {
356
+ std::vector<Torrent*> out;
357
+ out.reserve(torrents_.size());
358
+ for (auto& [hash, t] : torrents_) out.push_back(t.get());
359
+ return out;
360
+ }
361
+
362
+ Torrent* Client::add_torrent_file(const std::string& path, const std::string& save_path) {
363
+ auto info = TorrentInfo::from_file(path); // pure parse — safe off the reactor thread
364
+ if (!info) return nullptr;
365
+ return run_on_reactor([&] { return add_torrent_impl(*info, save_path); });
366
+ }
367
+
368
+ std::size_t Client::total_peers() const {
369
+ std::size_t n = 0;
370
+ for (const auto& [hash, t] : torrents_) n += t->num_peers();
371
+ return n;
372
+ }
373
+
374
+ void Client::schedule_reap() {
375
+ if (!opened_) return;
376
+ reap_timer_ = reactor_.schedule(std::chrono::seconds(1), [this] {
377
+ reap_closed();
378
+ sample_rates();
379
+ schedule_reap();
380
+ });
381
+ }
382
+
383
+ void Client::reap_closed() {
384
+ connections_.erase(
385
+ std::remove_if(connections_.begin(), connections_.end(),
386
+ [](const std::unique_ptr<PeerConnection>& pc) { return pc->closed(); }),
387
+ connections_.end());
388
+ }
389
+
390
+ void Client::sample_rates() {
391
+ std::uint64_t down = 0, up = 0;
392
+ for (auto& [hash, t] : torrents_) {
393
+ down += t->bytes_downloaded();
394
+ up += t->bytes_uploaded();
395
+ }
396
+ const auto now = std::chrono::steady_clock::now();
397
+ if (last_sample_.time_since_epoch().count() != 0) {
398
+ const double dt = std::chrono::duration<double>(now - last_sample_).count();
399
+ if (dt > 0) {
400
+ // Counters are monotonic, but guard against a torrent being removed
401
+ // between samples (which would make the aggregate drop).
402
+ const std::uint64_t d_down = down >= last_down_bytes_ ? down - last_down_bytes_ : 0;
403
+ const std::uint64_t d_up = up >= last_up_bytes_ ? up - last_up_bytes_ : 0;
404
+ down_rate_.store(std::uint64_t(double(d_down) / dt), std::memory_order_relaxed);
405
+ up_rate_.store(std::uint64_t(double(d_up) / dt), std::memory_order_relaxed);
406
+ }
407
+ }
408
+ last_down_bytes_ = down;
409
+ last_up_bytes_ = up;
410
+ last_sample_ = now;
411
+ }
412
+
413
+ } // namespace librats::bittorrent
@@ -0,0 +1,227 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file client.h
5
+ * @brief The BitTorrent session: owns the reactor, the listen socket and the
6
+ * set of torrents, and brokers peer connections between them.
7
+ *
8
+ * Client is the top-level handle an application drives. It accepts incoming peers
9
+ * and routes each (by the info-hash in its handshake) to the matching Torrent;
10
+ * it dials outgoing peers on a Torrent's behalf. All connections are owned here
11
+ * in one pool and reaped once closed, so torrents only ever hold raw pointers.
12
+ *
13
+ * Lifecycle: open() wires up the listener and timers on the reactor thread;
14
+ * start() additionally runs the reactor on its own thread. Tests instead pump
15
+ * reactor().run_one() so everything stays single-threaded and deterministic.
16
+ */
17
+
18
+ #include "librats/util/rats_export.h"
19
+ #include "librats/bittorrent/peer_connection.h"
20
+ #include "librats/bittorrent/reactor.h"
21
+ #include "librats/bittorrent/torrent.h"
22
+ #include "librats/bittorrent/torrent_info.h"
23
+ #include "librats/bittorrent/types.h"
24
+ #include "librats/core/socket.h"
25
+ #include "librats/core/types.h"
26
+ #include "librats/dht/dht.h"
27
+
28
+ #include <atomic>
29
+ #include <chrono>
30
+ #include <cstdint>
31
+ #include <functional>
32
+ #include <future>
33
+ #include <map>
34
+ #include <memory>
35
+ #include <mutex>
36
+ #include <string>
37
+ #include <type_traits>
38
+ #include <unordered_set>
39
+ #include <vector>
40
+
41
+ namespace librats::bittorrent {
42
+
43
+ /// A thread-safe snapshot of one torrent's state. Produced by Client::torrent_status
44
+ /// on the reactor thread so callers on any thread get a consistent view without
45
+ /// touching the (lock-free, reactor-owned) Torrent directly.
46
+ struct TorrentStatus {
47
+ struct File { std::string path; std::int64_t size = 0; };
48
+
49
+ bool exists = false; ///< false if no such torrent (rest is default)
50
+ std::string name;
51
+ bool has_metadata = false;
52
+ bool is_complete = false;
53
+ bool paused = false;
54
+ double progress = 0.0; ///< 0..1
55
+ std::uint64_t total_size = 0;
56
+ std::uint64_t downloaded = 0;
57
+ std::uint64_t uploaded = 0;
58
+ std::size_t num_peers = 0;
59
+ std::vector<File> files; ///< populated once metadata is known
60
+ };
61
+
62
+ class RATS_API Client final : public TorrentHost {
63
+ public:
64
+ struct Config {
65
+ std::uint16_t listen_port = 6881; ///< 0 = ephemeral
66
+ std::string download_path; ///< default save directory
67
+ std::string peer_id_prefix = "-LR0001-";
68
+ };
69
+
70
+ Client();
71
+ explicit Client(Config config);
72
+ ~Client() override;
73
+
74
+ Client(const Client&) = delete;
75
+ Client& operator=(const Client&) = delete;
76
+
77
+ /// Wire up the listener + housekeeping timers. Must run on the reactor thread
78
+ /// (call directly before pumping in tests, or via start()).
79
+ void open();
80
+ /// open() + run the reactor on a background thread.
81
+ void start();
82
+ /// Stop the reactor (joining its thread) and tear everything down.
83
+ void stop();
84
+
85
+ bool is_running() const noexcept { return opened_; }
86
+ std::uint16_t listen_port() const noexcept { return actual_port_; }
87
+ Reactor& reactor() noexcept { return reactor_; }
88
+
89
+ Torrent* add_torrent(const TorrentInfo& info, const std::string& save_path = "");
90
+ /// Add a magnet link — the torrent starts metadata-less and fetches its info
91
+ /// dict from peers (BEP 9) before downloading.
92
+ Torrent* add_magnet(const std::string& magnet_uri, const std::string& save_path = "");
93
+ /// Like add_magnet, but first loads any resume file saved next to @p save_path
94
+ /// (see Torrent::try_load_resume_data) so a previously-downloaded torrent
95
+ /// resumes its pieces — and, if the resume file embeds the info dict, skips the
96
+ /// metadata re-fetch entirely. Used to restore torrents across restarts.
97
+ Torrent* add_magnet_resumed(const std::string& magnet_uri, const std::string& save_path = "");
98
+ /// Add a torrent and apply saved resume state (trusts the recorded pieces).
99
+ Torrent* add_torrent_with_resume(const TorrentInfo& info, const ResumeData& resume,
100
+ const std::string& save_path = "");
101
+ /// Add a freshly-created torrent whose files already exist at @p save_path,
102
+ /// trusting every piece so it starts seeding without re-hashing.
103
+ Torrent* add_torrent_for_seeding(const TorrentInfo& info, const std::string& save_path);
104
+ /// Load a .torrent file from disk and add it. Returns nullptr if the file
105
+ /// cannot be read or parsed. Convenience over TorrentInfo::from_file + add_torrent.
106
+ Torrent* add_torrent_file(const std::string& path, const std::string& save_path = "");
107
+ Torrent* get_torrent(const InfoHash& info_hash);
108
+ void remove_torrent(const InfoHash& info_hash, bool delete_files = false);
109
+ std::vector<Torrent*> torrents();
110
+ /// Persist resume data for every torrent to its default path.
111
+ void save_all_resume_data();
112
+
113
+ // ---- thread-safe control / inspection (marshalled onto the reactor) ----
114
+ // Prefer these over reaching into a Torrent* from another thread: Torrent state
115
+ // is lock-free and reactor-owned, so it must only be touched on the reactor.
116
+
117
+ /// Consistent snapshot of one torrent (exists=false if not found).
118
+ TorrentStatus torrent_status(const InfoHash& info_hash);
119
+ /// Thread-safe copy of a loaded torrent's full metadata, or nullopt when the
120
+ /// torrent is unknown or its metadata hasn't arrived yet. Marshalled onto the
121
+ /// reactor, so it is safe to call from any thread — unlike get_torrent(), which
122
+ /// hands back a reactor-owned Torrent* that must not be touched off-thread.
123
+ std::optional<TorrentInfo> torrent_metadata(const InfoHash& info_hash);
124
+ /// Pause / resume a torrent without re-hashing (see Torrent::pause).
125
+ void pause_torrent(const InfoHash& info_hash);
126
+ void resume_torrent(const InfoHash& info_hash);
127
+ /// Persist one torrent's resume data to its default path. Returns false if the
128
+ /// torrent is unknown or the write failed.
129
+ bool save_resume_data(const InfoHash& info_hash);
130
+
131
+ // ---- aggregate stats (for status lines / UI) ----
132
+ std::size_t num_torrents() const noexcept { return torrents_.size(); }
133
+ std::size_t total_peers() const;
134
+ /// Swarm-wide transfer rates in bytes/sec, sampled once per second by the
135
+ /// housekeeping timer. Atomic so they can be read from another thread.
136
+ std::uint64_t total_download_rate() const noexcept { return down_rate_.load(std::memory_order_relaxed); }
137
+ std::uint64_t total_upload_rate() const noexcept { return up_rate_.load(std::memory_order_relaxed); }
138
+
139
+ /// Share an externally-owned DHT for peer discovery (e.g. the node's). Its
140
+ /// lifetime is the caller's; Client never starts or stops it.
141
+ void set_external_dht(DhtClient* dht) noexcept { dht_ = dht; }
142
+ DhtClient* get_dht_client() const noexcept { return dht_; }
143
+
144
+ // ---- TorrentHost ----
145
+ void connect_peer(Torrent& torrent, const std::string& ip, std::uint16_t port) override;
146
+ const PeerId& peer_id() const override { return peer_id_; }
147
+ void find_peers_via_dht(const InfoHash& info_hash,
148
+ std::function<void(const std::string& ip, std::uint16_t port)> on_peer) override;
149
+ void announce_to_dht(const InfoHash& info_hash, std::uint16_t port) override;
150
+
151
+ /// Largest number of peer connections (in + out) the session will hold at once.
152
+ /// Beyond this, inbound sockets are accepted and immediately closed so a flood
153
+ /// of incoming connections can't exhaust memory / file descriptors.
154
+ static constexpr std::size_t kMaxConnections = 200;
155
+
156
+ private:
157
+ void open_listener();
158
+ void on_accept();
159
+ void schedule_reap();
160
+ void reap_closed();
161
+ void sample_rates(); ///< recompute down_rate_/up_rate_ from per-torrent byte counters
162
+
163
+ // Run @p f on the reactor thread and return its result. Executed inline when
164
+ // already on the reactor thread or when the reactor isn't running its own
165
+ // thread yet (tests pump run_one); otherwise posted and waited on. This makes
166
+ // the public mutators below safe to call from any thread (C2).
167
+ template <class F>
168
+ std::invoke_result_t<F> run_on_reactor(F f) {
169
+ using R = std::invoke_result_t<F>;
170
+ if (reactor_.on_reactor_thread() || !reactor_.running()) return f();
171
+ std::promise<R> prom;
172
+ auto fut = prom.get_future();
173
+ reactor_.post([&] {
174
+ if constexpr (std::is_void_v<R>) { f(); prom.set_value(); }
175
+ else { prom.set_value(f()); }
176
+ });
177
+ return fut.get();
178
+ }
179
+
180
+ // Unsynchronised implementations — only ever run on the reactor thread (either
181
+ // directly in the single-threaded/test path or via run_on_reactor()).
182
+ Torrent* add_torrent_impl(const TorrentInfo& info, const std::string& save_path);
183
+ Torrent* add_magnet_impl(const std::string& magnet_uri, const std::string& save_path, bool resume);
184
+ Torrent* add_torrent_with_resume_impl(const TorrentInfo& info, const ResumeData& resume,
185
+ const std::string& save_path);
186
+ void remove_torrent_impl(const InfoHash& info_hash);
187
+
188
+ Reactor reactor_;
189
+ Config config_;
190
+ PeerId peer_id_;
191
+ socket_t listener_ = RATS_INVALID_SOCKET;
192
+ std::uint16_t actual_port_ = 0;
193
+ bool opened_ = false;
194
+ TimerId reap_timer_ = kInvalidTimerId;
195
+
196
+ DhtClient* dht_ = nullptr; ///< external, non-owning
197
+
198
+ /// Keeps DHT callbacks from touching a torn-down Client. A get_peers traversal
199
+ /// runs for seconds, and the shared DHT is stopped *after* us (node teardown is
200
+ /// reverse-attach order), so a reply can land once our reactor is gone and this
201
+ /// object destroyed — dereferencing it then is a use-after-free that hangs the
202
+ /// DHT loop thread inside Reactor::post, and with it the DHT's own join(). Every
203
+ /// callback we hand to the DHT captures this token by shared_ptr and does its
204
+ /// work under `mutex` with `alive` checked; stop() clears the flag under the same
205
+ /// mutex, so a callback either completes before teardown or does nothing at all.
206
+ struct DhtCallbackGuard {
207
+ std::mutex mutex;
208
+ bool alive = true;
209
+ };
210
+ std::shared_ptr<DhtCallbackGuard> dht_guard_ = std::make_shared<DhtCallbackGuard>();
211
+
212
+ std::map<InfoHash, std::unique_ptr<Torrent>> torrents_;
213
+ std::vector<std::unique_ptr<PeerConnection>> connections_;
214
+ /// Outbound sockets still waiting for connect() to complete. Tracked so a
215
+ /// mid-connect stop() can close them, and so the completion looks the torrent
216
+ /// up by info-hash rather than holding a raw Torrent* that may have been removed.
217
+ std::unordered_set<socket_t> pending_connects_;
218
+
219
+ // Rate sampling (updated on the reactor thread once per second).
220
+ std::atomic<std::uint64_t> down_rate_{0};
221
+ std::atomic<std::uint64_t> up_rate_{0};
222
+ std::uint64_t last_down_bytes_ = 0;
223
+ std::uint64_t last_up_bytes_ = 0;
224
+ std::chrono::steady_clock::time_point last_sample_{};
225
+ };
226
+
227
+ } // namespace librats::bittorrent