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
@@ -1,1415 +0,0 @@
1
- #include "file_transfer.h"
2
- #include "librats.h"
3
- #include "fs.h"
4
- #include "logger.h"
5
- #include "crypto/sha256.h"
6
-
7
- #include <algorithm>
8
- #include <cstring>
9
- #include <random>
10
-
11
- // Logging shorthand for this module.
12
- #define LOG_FT_INFO(msg) LOG_INFO("filetransfer", msg)
13
- #define LOG_FT_WARN(msg) LOG_WARN("filetransfer", msg)
14
- #define LOG_FT_ERROR(msg) LOG_ERROR("filetransfer", msg)
15
- #define LOG_FT_DEBUG(msg) LOG_DEBUG("filetransfer", msg)
16
-
17
- namespace librats {
18
-
19
- // =============================================================================
20
- // Wire constants
21
- // =============================================================================
22
-
23
- namespace {
24
-
25
- // Named control-message types exchanged on the JSON channel.
26
- constexpr const char* MSG_OFFER = "ft_offer";
27
- constexpr const char* MSG_RESPONSE = "ft_response";
28
- constexpr const char* MSG_FILE_END = "ft_file_end";
29
- constexpr const char* MSG_PROGRESS = "ft_progress";
30
- constexpr const char* MSG_COMPLETE = "ft_complete";
31
- constexpr const char* MSG_CONTROL = "ft_control";
32
-
33
- // Magic prefix of a binary chunk frame.
34
- constexpr char CHUNK_MAGIC[4] = {'R', 'F', 'T', '1'};
35
-
36
- // How long a finished transfer is kept queryable before being purged.
37
- constexpr auto FINISHED_RETENTION = std::chrono::minutes(5);
38
-
39
- // =============================================================================
40
- // Small helpers
41
- // =============================================================================
42
-
43
- // --- big-endian serialization ---
44
- void put_u16(std::vector<uint8_t>& b, uint16_t v) {
45
- b.push_back(uint8_t(v >> 8));
46
- b.push_back(uint8_t(v));
47
- }
48
- void put_u32(std::vector<uint8_t>& b, uint32_t v) {
49
- b.push_back(uint8_t(v >> 24));
50
- b.push_back(uint8_t(v >> 16));
51
- b.push_back(uint8_t(v >> 8));
52
- b.push_back(uint8_t(v));
53
- }
54
- void put_u64(std::vector<uint8_t>& b, uint64_t v) {
55
- for (int s = 56; s >= 0; s -= 8) b.push_back(uint8_t(v >> s));
56
- }
57
- uint16_t get_u16(const uint8_t* p) { return uint16_t(p[0]) << 8 | p[1]; }
58
- uint32_t get_u32(const uint8_t* p) {
59
- return uint32_t(p[0]) << 24 | uint32_t(p[1]) << 16 | uint32_t(p[2]) << 8 | p[3];
60
- }
61
- uint64_t get_u64(const uint8_t* p) {
62
- uint64_t v = 0;
63
- for (int i = 0; i < 8; ++i) v = (v << 8) | p[i];
64
- return v;
65
- }
66
-
67
- // --- CRC32 (IEEE 802.3) for per-chunk integrity ---
68
- struct Crc32Table {
69
- uint32_t t[256];
70
- Crc32Table() {
71
- for (uint32_t i = 0; i < 256; ++i) {
72
- uint32_t c = i;
73
- for (int k = 0; k < 8; ++k) c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
74
- t[i] = c;
75
- }
76
- }
77
- };
78
- uint32_t crc32(const uint8_t* data, size_t len) {
79
- static const Crc32Table tbl;
80
- uint32_t c = 0xFFFFFFFFu;
81
- for (size_t i = 0; i < len; ++i) c = tbl.t[(c ^ data[i]) & 0xFF] ^ (c >> 8);
82
- return c ^ 0xFFFFFFFFu;
83
- }
84
-
85
- // --- hex encoding ---
86
- std::string to_hex(const uint8_t* d, size_t n) {
87
- static const char* h = "0123456789abcdef";
88
- std::string s(n * 2, '0');
89
- for (size_t i = 0; i < n; ++i) {
90
- s[2 * i] = h[d[i] >> 4];
91
- s[2 * i + 1] = h[d[i] & 0xF];
92
- }
93
- return s;
94
- }
95
-
96
- // SHA-256 of an empty input, used for zero-byte files.
97
- std::string sha256_of_empty() {
98
- uint8_t digest[SHA256_HASH_SIZE];
99
- sha256_hash(digest, nullptr, 0);
100
- return to_hex(digest, SHA256_HASH_SIZE);
101
- }
102
-
103
- // --- random transfer id ---
104
- std::string random_transfer_id() {
105
- static const char* h = "0123456789abcdef";
106
- std::random_device rd;
107
- std::mt19937 gen(rd() ^ uint32_t(std::chrono::steady_clock::now().time_since_epoch().count()));
108
- std::uniform_int_distribution<int> dis(0, 15);
109
- std::string id(32, '0');
110
- for (char& c : id) c = h[dis(gen)];
111
- return id;
112
- }
113
-
114
- // A relative path coming from a peer must not escape the destination directory.
115
- bool is_safe_relative_path(const std::string& p) {
116
- if (p.empty()) return false;
117
- if (p.front() == '/' || p.front() == '\\') return false;
118
- if (p.size() >= 2 && p[1] == ':') return false; // Windows drive letter
119
- size_t start = 0;
120
- for (size_t i = 0; i <= p.size(); ++i) {
121
- if (i == p.size() || p[i] == '/' || p[i] == '\\') {
122
- std::string comp = p.substr(start, i - start);
123
- if (comp.empty() || comp == "." || comp == "..") return false;
124
- start = i + 1;
125
- }
126
- }
127
- return true;
128
- }
129
-
130
- } // namespace
131
-
132
- // =============================================================================
133
- // Internal per-transfer state
134
- // =============================================================================
135
-
136
- // One file within a transfer.
137
- struct TransferFile {
138
- std::string relative_path; // POSIX path relative to the transfer root
139
- uint64_t size = 0;
140
-
141
- // sender side
142
- std::string source_path; // local path the data is read from
143
-
144
- // receiver side
145
- std::string temp_path; // chunks are written here first
146
- std::string final_path; // destination once verified
147
- uint64_t received = 0; // bytes written so far
148
- bool temp_created = false;
149
- std::string expected_sha; // from ft_file_end
150
- std::string computed_sha; // hashed while receiving
151
- bool sha_known = false;
152
- bool finalized = false;
153
- };
154
-
155
- // Full state of a single transfer. Always held through a shared_ptr; `mtx`
156
- // guards every field below `total_bytes`.
157
- struct Transfer {
158
- // immutable after creation
159
- std::string id;
160
- std::string peer_id;
161
- FileTransferDirection direction;
162
- bool is_directory = false;
163
- std::string name;
164
- std::string local_root; // sender: source path; receiver: destination
165
- std::vector<TransferFile> files;
166
- uint64_t total_bytes = 0;
167
-
168
- // mutable state
169
- std::mutex mtx;
170
- std::condition_variable cv;
171
- FileTransferStatus status = FileTransferStatus::PENDING;
172
- bool finished = false; // finish() has run (fired callbacks); distinct from status
173
- std::string error;
174
- uint64_t bytes_done = 0; // sender: streamed bytes; receiver: written bytes
175
- uint64_t acked_bytes = 0; // sender: bytes confirmed by the peer
176
- uint32_t files_done = 0;
177
-
178
- // sender streaming cursor (survives pause)
179
- size_t send_file = 0;
180
- uint64_t send_offset = 0;
181
- sha256_context_t send_hash;
182
- bool worker_active = false;
183
-
184
- // receiver cursor
185
- size_t recv_file = 0;
186
- sha256_context_t recv_hash;
187
- uint64_t last_ack_sent = 0; // bytes_done at the last ft_progress emitted
188
-
189
- // timing
190
- std::chrono::steady_clock::time_point start_time;
191
- std::chrono::steady_clock::time_point last_activity;
192
- std::chrono::steady_clock::time_point last_progress_cb;
193
-
194
- // throughput
195
- double rate_bps = 0.0;
196
- uint64_t rate_mark_bytes = 0;
197
- std::chrono::steady_clock::time_point rate_mark_time;
198
-
199
- bool is_terminal() const {
200
- return status == FileTransferStatus::COMPLETED ||
201
- status == FileTransferStatus::FAILED ||
202
- status == FileTransferStatus::CANCELLED;
203
- }
204
- };
205
-
206
- // =============================================================================
207
- // Status name
208
- // =============================================================================
209
-
210
- const char* file_transfer_status_name(FileTransferStatus s) {
211
- switch (s) {
212
- case FileTransferStatus::PENDING: return "PENDING";
213
- case FileTransferStatus::STARTING: return "STARTING";
214
- case FileTransferStatus::IN_PROGRESS: return "IN_PROGRESS";
215
- case FileTransferStatus::PAUSED: return "PAUSED";
216
- case FileTransferStatus::COMPLETED: return "COMPLETED";
217
- case FileTransferStatus::FAILED: return "FAILED";
218
- case FileTransferStatus::CANCELLED: return "CANCELLED";
219
- case FileTransferStatus::RESUMING: return "RESUMING";
220
- }
221
- return "UNKNOWN";
222
- }
223
-
224
- // =============================================================================
225
- // Construction / destruction
226
- // =============================================================================
227
-
228
- FileTransferManager::FileTransferManager(RatsClient& client, const FileTransferConfig& config)
229
- : client_(client), config_(config) {
230
- started_at_ = std::chrono::steady_clock::now();
231
- create_directories(config_.temp_directory.c_str());
232
- register_handlers();
233
-
234
- uint32_t threads = std::max<uint32_t>(1, config_.worker_threads);
235
- for (uint32_t i = 0; i < threads; ++i) {
236
- workers_.emplace_back(&FileTransferManager::worker_loop, this);
237
- }
238
- maintenance_thread_ = std::thread(&FileTransferManager::maintenance_loop, this);
239
-
240
- LOG_FT_INFO("FileTransferManager started (" << threads << " workers)");
241
- }
242
-
243
- FileTransferManager::~FileTransferManager() {
244
- running_.store(false);
245
- queue_cv_.notify_all();
246
- maintenance_cv_.notify_all();
247
-
248
- // Wake any worker blocked on a transfer's condition variable.
249
- {
250
- std::lock_guard<std::mutex> lk(transfers_mutex_);
251
- for (auto& kv : transfers_) {
252
- std::lock_guard<std::mutex> tk(kv.second->mtx);
253
- kv.second->cv.notify_all();
254
- }
255
- }
256
-
257
- for (auto& w : workers_) {
258
- if (w.joinable()) w.join();
259
- }
260
- if (maintenance_thread_.joinable()) maintenance_thread_.join();
261
- LOG_FT_INFO("FileTransferManager stopped");
262
- }
263
-
264
- void FileTransferManager::register_handlers() {
265
- client_.on(MSG_OFFER, [this](const std::string& p, const nlohmann::json& m) { on_offer(p, m); });
266
- client_.on(MSG_RESPONSE, [this](const std::string& p, const nlohmann::json& m) { on_response(p, m); });
267
- client_.on(MSG_FILE_END, [this](const std::string& p, const nlohmann::json& m) { on_file_end(p, m); });
268
- client_.on(MSG_PROGRESS, [this](const std::string& p, const nlohmann::json& m) { on_progress(p, m); });
269
- client_.on(MSG_COMPLETE, [this](const std::string& p, const nlohmann::json& m) { on_complete(p, m); });
270
- client_.on(MSG_CONTROL, [this](const std::string& p, const nlohmann::json& m) { on_control(p, m); });
271
- }
272
-
273
- // =============================================================================
274
- // Configuration
275
- // =============================================================================
276
-
277
- void FileTransferManager::set_config(const FileTransferConfig& config) {
278
- std::lock_guard<std::mutex> lk(config_mutex_);
279
- config_ = config;
280
- create_directories(config_.temp_directory.c_str());
281
- }
282
-
283
- FileTransferConfig FileTransferManager::get_config() const {
284
- std::lock_guard<std::mutex> lk(config_mutex_);
285
- return config_;
286
- }
287
-
288
- // =============================================================================
289
- // Callbacks
290
- // =============================================================================
291
-
292
- void FileTransferManager::set_offer_callback(TransferOfferCallback cb) {
293
- std::lock_guard<std::mutex> lk(callbacks_mutex_);
294
- offer_callback_ = std::move(cb);
295
- }
296
- void FileTransferManager::set_progress_callback(TransferProgressCallback cb) {
297
- std::lock_guard<std::mutex> lk(callbacks_mutex_);
298
- progress_callback_ = std::move(cb);
299
- }
300
- void FileTransferManager::set_completed_callback(TransferCompletedCallback cb) {
301
- std::lock_guard<std::mutex> lk(callbacks_mutex_);
302
- completed_callback_ = std::move(cb);
303
- }
304
-
305
- // =============================================================================
306
- // Lookup / progress snapshot
307
- // =============================================================================
308
-
309
- std::shared_ptr<Transfer> FileTransferManager::find(const std::string& id) const {
310
- std::lock_guard<std::mutex> lk(transfers_mutex_);
311
- auto it = transfers_.find(id);
312
- return it == transfers_.end() ? nullptr : it->second;
313
- }
314
-
315
- // Builds a progress snapshot. Caller must hold t->mtx.
316
- FileTransferProgress FileTransferManager::snapshot(const std::shared_ptr<Transfer>& t) const {
317
- FileTransferProgress p;
318
- p.transfer_id = t->id;
319
- p.peer_id = t->peer_id;
320
- p.direction = t->direction;
321
- p.status = t->status;
322
- p.filename = t->name;
323
- p.local_path = t->local_root;
324
- p.is_directory = t->is_directory;
325
- p.bytes_transferred = t->bytes_done;
326
- p.total_bytes = t->total_bytes;
327
- p.files_completed = t->files_done;
328
- p.total_files = static_cast<uint32_t>(t->files.size());
329
- p.transfer_rate_bps = t->rate_bps;
330
- p.error_message = t->error;
331
-
332
- auto now = std::chrono::steady_clock::now();
333
- auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - t->start_time);
334
- p.elapsed_time = elapsed;
335
- if (elapsed.count() > 0) {
336
- p.average_rate_bps = static_cast<double>(t->bytes_done) * 1000.0 / elapsed.count();
337
- }
338
- if (t->rate_bps > 1.0 && t->total_bytes > t->bytes_done) {
339
- double secs = static_cast<double>(t->total_bytes - t->bytes_done) / t->rate_bps;
340
- p.estimated_time_remaining = std::chrono::milliseconds(static_cast<int64_t>(secs * 1000.0));
341
- }
342
- return p;
343
- }
344
-
345
- std::shared_ptr<FileTransferProgress>
346
- FileTransferManager::get_progress(const std::string& id) const {
347
- auto t = find(id);
348
- if (!t) return nullptr;
349
- std::lock_guard<std::mutex> lk(t->mtx);
350
- return std::make_shared<FileTransferProgress>(snapshot(t));
351
- }
352
-
353
- std::vector<std::shared_ptr<FileTransferProgress>>
354
- FileTransferManager::get_active_transfers() const {
355
- std::vector<std::shared_ptr<Transfer>> all;
356
- {
357
- std::lock_guard<std::mutex> lk(transfers_mutex_);
358
- for (auto& kv : transfers_) all.push_back(kv.second);
359
- }
360
- std::vector<std::shared_ptr<FileTransferProgress>> out;
361
- for (auto& t : all) {
362
- std::lock_guard<std::mutex> lk(t->mtx);
363
- if (!t->is_terminal()) {
364
- out.push_back(std::make_shared<FileTransferProgress>(snapshot(t)));
365
- }
366
- }
367
- return out;
368
- }
369
-
370
- nlohmann::json FileTransferManager::get_statistics() const {
371
- nlohmann::json j;
372
- {
373
- std::lock_guard<std::mutex> lk(stats_mutex_);
374
- auto uptime = std::chrono::duration_cast<std::chrono::seconds>(
375
- std::chrono::steady_clock::now() - started_at_);
376
- j["uptime_seconds"] = uptime.count();
377
- j["total_bytes_sent"] = stat_bytes_sent_;
378
- j["total_bytes_received"] = stat_bytes_received_;
379
- j["total_files_sent"] = stat_files_sent_;
380
- j["total_files_received"] = stat_files_received_;
381
- j["completed_transfers"] = stat_completed_;
382
- j["failed_transfers"] = stat_failed_;
383
- uint64_t total = stat_completed_ + stat_failed_;
384
- j["success_rate"] = total ? static_cast<double>(stat_completed_) / total : 0.0;
385
- }
386
- {
387
- std::lock_guard<std::mutex> lk(transfers_mutex_);
388
- size_t active = 0;
389
- for (auto& kv : transfers_) {
390
- std::lock_guard<std::mutex> tk(kv.second->mtx);
391
- if (!kv.second->is_terminal()) ++active;
392
- }
393
- j["active_transfers"] = active;
394
- }
395
- return j;
396
- }
397
-
398
- // =============================================================================
399
- // Progress / completion notification
400
- // =============================================================================
401
-
402
- void FileTransferManager::emit_progress(const std::shared_ptr<Transfer>& t) {
403
- FileTransferProgress snap;
404
- {
405
- std::lock_guard<std::mutex> lk(t->mtx);
406
- auto now = std::chrono::steady_clock::now();
407
-
408
- // Refresh the throughput estimate roughly twice a second.
409
- auto dt = std::chrono::duration_cast<std::chrono::milliseconds>(now - t->rate_mark_time);
410
- if (dt.count() >= 500) {
411
- t->rate_bps = static_cast<double>(t->bytes_done - t->rate_mark_bytes) * 1000.0 / dt.count();
412
- t->rate_mark_bytes = t->bytes_done;
413
- t->rate_mark_time = now;
414
- }
415
-
416
- // Throttle callbacks to ~10/s unless the transfer just finished.
417
- auto since_cb = std::chrono::duration_cast<std::chrono::milliseconds>(now - t->last_progress_cb);
418
- if (!t->is_terminal() && since_cb.count() < 100) return;
419
- t->last_progress_cb = now;
420
-
421
- snap = snapshot(t);
422
- }
423
- TransferProgressCallback cb;
424
- {
425
- std::lock_guard<std::mutex> lk(callbacks_mutex_);
426
- cb = progress_callback_;
427
- }
428
- if (cb) cb(snap);
429
- }
430
-
431
- void FileTransferManager::finish(const std::shared_ptr<Transfer>& t, bool success,
432
- const std::string& error) {
433
- FileTransferProgress snap;
434
- bool is_receiver;
435
- size_t file_count;
436
- {
437
- std::lock_guard<std::mutex> lk(t->mtx);
438
- if (t->finished) return; // finish() already ran for this transfer
439
- t->finished = true;
440
- // Preserve an explicit CANCELLED status set by the cancel path.
441
- if (!success && t->status == FileTransferStatus::CANCELLED) {
442
- // keep CANCELLED
443
- } else {
444
- t->status = success ? FileTransferStatus::COMPLETED : FileTransferStatus::FAILED;
445
- }
446
- t->error = error;
447
- t->last_activity = std::chrono::steady_clock::now();
448
- t->last_progress_cb = {}; // force the final progress callback through
449
- is_receiver = (t->direction == FileTransferDirection::RECEIVING);
450
- file_count = t->files.size();
451
- t->cv.notify_all();
452
- snap = snapshot(t);
453
- }
454
-
455
- if (!success && is_receiver) {
456
- cleanup_temp_files(t);
457
- }
458
-
459
- {
460
- std::lock_guard<std::mutex> lk(stats_mutex_);
461
- if (success) {
462
- ++stat_completed_;
463
- if (is_receiver) stat_files_received_ += file_count;
464
- else stat_files_sent_ += file_count;
465
- } else {
466
- ++stat_failed_;
467
- }
468
- }
469
-
470
- TransferProgressCallback pcb;
471
- TransferCompletedCallback ccb;
472
- {
473
- std::lock_guard<std::mutex> lk(callbacks_mutex_);
474
- pcb = progress_callback_;
475
- ccb = completed_callback_;
476
- }
477
- if (pcb) pcb(snap);
478
- if (ccb) ccb(t->id, success, error);
479
-
480
- LOG_FT_INFO("Transfer " << t->id << " " << (success ? "completed" : "ended")
481
- << (error.empty() ? "" : (": " + error)));
482
- }
483
-
484
- void FileTransferManager::cleanup_temp_files(const std::shared_ptr<Transfer>& t) {
485
- std::vector<std::string> temps;
486
- {
487
- std::lock_guard<std::mutex> lk(t->mtx);
488
- for (auto& f : t->files) {
489
- if (f.temp_created && !f.finalized) temps.push_back(f.temp_path);
490
- }
491
- }
492
- for (auto& p : temps) {
493
- if (file_exists(p)) delete_file(p.c_str());
494
- }
495
- }
496
-
497
- // =============================================================================
498
- // Sending: send_file / send_directory
499
- // =============================================================================
500
-
501
- namespace {
502
- // Recursively collects regular files under `abs_dir` into `out`.
503
- void scan_directory(const std::string& abs_dir, const std::string& rel_prefix,
504
- std::vector<TransferFile>& out) {
505
- std::vector<DirectoryEntry> entries;
506
- if (!list_directory(abs_dir.c_str(), entries)) return;
507
- for (const auto& e : entries) {
508
- std::string rel = rel_prefix.empty() ? e.name : rel_prefix + "/" + e.name;
509
- if (e.is_directory) {
510
- scan_directory(e.path, rel, out);
511
- } else {
512
- TransferFile f;
513
- f.relative_path = rel;
514
- int64_t sz = get_file_size(e.path.c_str());
515
- f.size = sz > 0 ? static_cast<uint64_t>(sz) : 0;
516
- f.source_path = e.path;
517
- out.push_back(std::move(f));
518
- }
519
- }
520
- }
521
- } // namespace
522
-
523
- std::string FileTransferManager::send_file(const std::string& peer_id, const std::string& file_path,
524
- const std::string& remote_name) {
525
- if (!file_exists(file_path) || is_directory(file_path.c_str())) {
526
- LOG_FT_ERROR("send_file: not a readable file: " << file_path);
527
- return "";
528
- }
529
- int64_t sz = get_file_size(file_path.c_str());
530
- if (sz < 0) {
531
- LOG_FT_ERROR("send_file: cannot stat: " << file_path);
532
- return "";
533
- }
534
- std::string name = remote_name.empty() ? get_filename_from_path(file_path) : remote_name;
535
-
536
- auto t = std::make_shared<Transfer>();
537
- t->id = random_transfer_id();
538
- t->peer_id = peer_id;
539
- t->direction = FileTransferDirection::SENDING;
540
- t->is_directory = false;
541
- t->name = name;
542
- t->local_root = file_path;
543
- t->total_bytes = static_cast<uint64_t>(sz);
544
-
545
- TransferFile f;
546
- f.relative_path = name;
547
- f.size = t->total_bytes;
548
- f.source_path = file_path;
549
- t->files.push_back(std::move(f));
550
-
551
- return start_send(peer_id, t, name);
552
- }
553
-
554
- std::string FileTransferManager::send_directory(const std::string& peer_id,
555
- const std::string& directory_path,
556
- const std::string& remote_name) {
557
- if (!directory_exists(directory_path)) {
558
- LOG_FT_ERROR("send_directory: not a directory: " << directory_path);
559
- return "";
560
- }
561
- std::string name = remote_name.empty() ? get_filename_from_path(directory_path) : remote_name;
562
-
563
- auto t = std::make_shared<Transfer>();
564
- t->id = random_transfer_id();
565
- t->peer_id = peer_id;
566
- t->direction = FileTransferDirection::SENDING;
567
- t->is_directory = true;
568
- t->name = name;
569
- t->local_root = directory_path;
570
-
571
- scan_directory(directory_path, "", t->files);
572
- for (auto& f : t->files) t->total_bytes += f.size;
573
-
574
- return start_send(peer_id, t, name);
575
- }
576
-
577
- // Registers a freshly-built sending transfer and sends its offer.
578
- std::string FileTransferManager::start_send(const std::string& peer_id,
579
- const std::shared_ptr<Transfer>& t,
580
- const std::string& /*name*/) {
581
- t->status = FileTransferStatus::STARTING;
582
- t->start_time = std::chrono::steady_clock::now();
583
- t->last_activity = t->start_time;
584
- t->rate_mark_time = t->start_time;
585
-
586
- {
587
- std::lock_guard<std::mutex> lk(transfers_mutex_);
588
- transfers_[t->id] = t;
589
- }
590
-
591
- nlohmann::json offer;
592
- offer["transfer_id"] = t->id;
593
- offer["name"] = t->name;
594
- offer["is_directory"] = t->is_directory;
595
- offer["total_size"] = t->total_bytes;
596
- nlohmann::json files = nlohmann::json::array();
597
- for (const auto& f : t->files) {
598
- files.push_back({{"path", f.relative_path}, {"size", f.size}});
599
- }
600
- offer["files"] = files;
601
-
602
- client_.send(peer_id, MSG_OFFER, offer);
603
- LOG_FT_INFO("Offering " << (t->is_directory ? "directory '" : "file '") << t->name
604
- << "' (" << t->files.size() << " file(s), " << t->total_bytes
605
- << " bytes) to " << peer_id << " [" << t->id << "]");
606
- return t->id;
607
- }
608
-
609
-
610
- // =============================================================================
611
- // Sending: streaming worker
612
- // =============================================================================
613
-
614
- void FileTransferManager::queue_send(const std::string& id) {
615
- {
616
- std::lock_guard<std::mutex> lk(queue_mutex_);
617
- send_queue_.push(id);
618
- }
619
- queue_cv_.notify_one();
620
- }
621
-
622
- void FileTransferManager::worker_loop() {
623
- while (running_.load()) {
624
- std::string id;
625
- {
626
- std::unique_lock<std::mutex> lk(queue_mutex_);
627
- queue_cv_.wait(lk, [this] { return !running_.load() || !send_queue_.empty(); });
628
- if (!running_.load()) return;
629
- id = send_queue_.front();
630
- send_queue_.pop();
631
- }
632
- auto t = find(id);
633
- if (!t) continue;
634
-
635
- {
636
- std::lock_guard<std::mutex> lk(t->mtx);
637
- if (t->worker_active) continue; // another worker already owns it
638
- t->worker_active = true;
639
- }
640
- run_send(t);
641
- {
642
- std::lock_guard<std::mutex> lk(t->mtx);
643
- t->worker_active = false;
644
- }
645
- }
646
- }
647
-
648
- void FileTransferManager::run_send(const std::shared_ptr<Transfer>& t) {
649
- FileTransferConfig cfg = get_config();
650
- std::vector<uint8_t> buf(cfg.chunk_size);
651
-
652
- while (running_.load()) {
653
- size_t file_index;
654
- uint64_t offset;
655
- uint64_t file_size;
656
- std::string source_path;
657
- {
658
- std::unique_lock<std::mutex> lk(t->mtx);
659
- if (t->status == FileTransferStatus::RESUMING) {
660
- t->status = FileTransferStatus::IN_PROGRESS;
661
- }
662
- if (t->status == FileTransferStatus::PAUSED) return; // cursor preserved
663
- if (t->status != FileTransferStatus::IN_PROGRESS) return; // cancelled / failed
664
- if (t->send_file >= t->files.size()) break; // all data sent
665
-
666
- file_index = t->send_file;
667
- offset = t->send_offset;
668
- file_size = t->files[file_index].size;
669
- source_path = t->files[file_index].source_path;
670
- if (offset == 0) sha256_reset(&t->send_hash);
671
- }
672
-
673
- // Empty file: no chunks, just the end marker.
674
- if (file_size == 0) {
675
- nlohmann::json end{{"transfer_id", t->id}, {"file_index", file_index},
676
- {"sha256", sha256_of_empty()}};
677
- client_.send(t->peer_id, MSG_FILE_END, end);
678
- std::lock_guard<std::mutex> lk(t->mtx);
679
- t->send_file++;
680
- t->send_offset = 0;
681
- t->files_done++;
682
- continue;
683
- }
684
-
685
- uint32_t want = static_cast<uint32_t>(std::min<uint64_t>(cfg.chunk_size, file_size - offset));
686
- if (!read_file_chunk(source_path, offset, buf.data(), want)) {
687
- nlohmann::json done{{"transfer_id", t->id}, {"success", false},
688
- {"error", "sender failed to read file"}};
689
- client_.send(t->peer_id, MSG_COMPLETE, done);
690
- finish(t, false, "failed to read " + source_path);
691
- return;
692
- }
693
-
694
- uint32_t crc = crc32(buf.data(), want);
695
-
696
- // Build the chunk frame: magic | id | file_index | offset | len | crc | data.
697
- std::vector<uint8_t> frame;
698
- frame.reserve(4 + 2 + t->id.size() + 4 + 8 + 4 + 4 + want);
699
- frame.insert(frame.end(), CHUNK_MAGIC, CHUNK_MAGIC + 4);
700
- put_u16(frame, static_cast<uint16_t>(t->id.size()));
701
- frame.insert(frame.end(), t->id.begin(), t->id.end());
702
- put_u32(frame, static_cast<uint32_t>(file_index));
703
- put_u64(frame, offset);
704
- put_u32(frame, want);
705
- put_u32(frame, crc);
706
- frame.insert(frame.end(), buf.data(), buf.data() + want);
707
-
708
- bool sent = client_.send_binary_to_peer_id(t->peer_id, frame, MessageDataType::BINARY);
709
- if (!sent) {
710
- finish(t, false, "connection lost while sending");
711
- return;
712
- }
713
-
714
- bool file_done = false;
715
- std::string sha_hex;
716
- {
717
- std::lock_guard<std::mutex> lk(t->mtx);
718
- sha256_update(&t->send_hash, buf.data(), want);
719
- t->send_offset += want;
720
- t->bytes_done += want;
721
- t->last_activity = std::chrono::steady_clock::now();
722
- if (t->send_offset >= file_size) {
723
- uint8_t digest[SHA256_HASH_SIZE];
724
- sha256_finish(&t->send_hash, digest);
725
- sha_hex = to_hex(digest, SHA256_HASH_SIZE);
726
- t->send_file++;
727
- t->send_offset = 0;
728
- t->files_done++;
729
- file_done = true;
730
- }
731
- }
732
- {
733
- std::lock_guard<std::mutex> lk(stats_mutex_);
734
- stat_bytes_sent_ += want;
735
- }
736
- if (file_done) {
737
- nlohmann::json end{{"transfer_id", t->id}, {"file_index", file_index},
738
- {"sha256", sha_hex}};
739
- client_.send(t->peer_id, MSG_FILE_END, end);
740
- }
741
-
742
- emit_progress(t);
743
-
744
- // Backpressure: do not get more than `window_bytes` ahead of the peer.
745
- {
746
- std::unique_lock<std::mutex> lk(t->mtx);
747
- while (running_.load() && t->status == FileTransferStatus::IN_PROGRESS &&
748
- t->bytes_done - t->acked_bytes >= cfg.window_bytes) {
749
- t->cv.wait_for(lk, std::chrono::milliseconds(200));
750
- }
751
- }
752
- }
753
-
754
- if (!running_.load()) return;
755
-
756
- // All data streamed; wait for the receiver's ft_complete.
757
- {
758
- std::unique_lock<std::mutex> lk(t->mtx);
759
- while (running_.load() && t->status == FileTransferStatus::IN_PROGRESS) {
760
- t->cv.wait_for(lk, std::chrono::milliseconds(200));
761
- }
762
- }
763
- }
764
-
765
- // =============================================================================
766
- // Receiving: offer handling
767
- // =============================================================================
768
-
769
- void FileTransferManager::on_offer(const std::string& peer_id, const nlohmann::json& msg) {
770
- try {
771
- std::string id = msg.at("transfer_id").get<std::string>();
772
- if (find(id)) return; // duplicate offer
773
-
774
- auto t = std::make_shared<Transfer>();
775
- t->id = id;
776
- t->peer_id = peer_id;
777
- t->direction = FileTransferDirection::RECEIVING;
778
- t->is_directory = msg.value("is_directory", false);
779
- t->name = msg.value("name", std::string("transfer"));
780
- t->total_bytes = msg.value("total_size", uint64_t(0));
781
- t->status = FileTransferStatus::PENDING;
782
- t->start_time = std::chrono::steady_clock::now();
783
- t->last_activity = t->start_time;
784
- t->rate_mark_time = t->start_time;
785
-
786
- bool unsafe = false;
787
- for (const auto& fj : msg.at("files")) {
788
- TransferFile f;
789
- f.relative_path = fj.at("path").get<std::string>();
790
- f.size = fj.value("size", uint64_t(0));
791
- if (!is_safe_relative_path(f.relative_path)) unsafe = true;
792
- t->files.push_back(std::move(f));
793
- }
794
-
795
- if (unsafe) {
796
- LOG_FT_WARN("Rejecting offer " << id << " from " << peer_id << ": unsafe path in manifest");
797
- nlohmann::json resp{{"transfer_id", id}, {"accepted", false},
798
- {"reason", "unsafe path in manifest"}};
799
- client_.send(peer_id, MSG_RESPONSE, resp);
800
- return;
801
- }
802
-
803
- {
804
- std::lock_guard<std::mutex> lk(transfers_mutex_);
805
- transfers_[id] = t;
806
- }
807
-
808
- IncomingTransferOffer offer;
809
- offer.transfer_id = id;
810
- offer.peer_id = peer_id;
811
- offer.name = t->name;
812
- offer.is_directory = t->is_directory;
813
- offer.total_size = t->total_bytes;
814
- for (const auto& f : t->files) offer.files.push_back({f.relative_path, f.size});
815
-
816
- LOG_FT_INFO("Incoming offer [" << id << "] '" << t->name << "' from " << peer_id
817
- << " (" << t->files.size() << " file(s), "
818
- << t->total_bytes << " bytes)");
819
-
820
- TransferOfferCallback cb;
821
- {
822
- std::lock_guard<std::mutex> lk(callbacks_mutex_);
823
- cb = offer_callback_;
824
- }
825
- if (cb) {
826
- cb(offer);
827
- } else {
828
- reject(id, "no offer handler configured");
829
- }
830
- } catch (const std::exception& e) {
831
- LOG_FT_ERROR("Malformed ft_offer from " << peer_id << ": " << e.what());
832
- }
833
- }
834
-
835
- bool FileTransferManager::accept(const std::string& transfer_id, const std::string& local_path) {
836
- auto t = find(transfer_id);
837
- if (!t || t->direction != FileTransferDirection::RECEIVING) return false;
838
-
839
- FileTransferConfig cfg = get_config();
840
- bool empty_transfer = false;
841
- {
842
- std::lock_guard<std::mutex> lk(t->mtx);
843
- if (t->status != FileTransferStatus::PENDING) return false;
844
- t->local_root = local_path;
845
- for (size_t i = 0; i < t->files.size(); ++i) {
846
- TransferFile& f = t->files[i];
847
- f.final_path = t->is_directory ? combine_paths(local_path, f.relative_path)
848
- : local_path;
849
- f.temp_path = combine_paths(cfg.temp_directory,
850
- transfer_id + "." + std::to_string(i) + ".part");
851
- }
852
- t->status = FileTransferStatus::IN_PROGRESS;
853
- t->start_time = std::chrono::steady_clock::now();
854
- t->last_activity = t->start_time;
855
- t->rate_mark_time = t->start_time;
856
- empty_transfer = t->files.empty();
857
- }
858
-
859
- create_directories(cfg.temp_directory.c_str());
860
- nlohmann::json resp{{"transfer_id", transfer_id}, {"accepted", true}};
861
- client_.send(t->peer_id, MSG_RESPONSE, resp);
862
- LOG_FT_INFO("Accepted transfer [" << transfer_id << "] -> " << local_path);
863
-
864
- if (t->is_directory) create_directories(local_path.c_str());
865
-
866
- emit_progress(t);
867
-
868
- // A transfer with no files (e.g. an empty directory) is done immediately.
869
- if (empty_transfer) {
870
- nlohmann::json done{{"transfer_id", transfer_id}, {"success", true}};
871
- client_.send(t->peer_id, MSG_COMPLETE, done);
872
- finish(t, true, "");
873
- }
874
- return true;
875
- }
876
-
877
- bool FileTransferManager::reject(const std::string& transfer_id, const std::string& reason) {
878
- auto t = find(transfer_id);
879
- if (!t || t->direction != FileTransferDirection::RECEIVING) return false;
880
- {
881
- std::lock_guard<std::mutex> lk(t->mtx);
882
- if (t->status != FileTransferStatus::PENDING) return false;
883
- t->status = FileTransferStatus::CANCELLED;
884
- }
885
- nlohmann::json resp{{"transfer_id", transfer_id}, {"accepted", false}, {"reason", reason}};
886
- client_.send(t->peer_id, MSG_RESPONSE, resp);
887
- finish(t, false, reason.empty() ? "rejected" : ("rejected: " + reason));
888
- return true;
889
- }
890
-
891
- // =============================================================================
892
- // Receiving: chunk handling
893
- // =============================================================================
894
-
895
- bool FileTransferManager::handle_binary_data(const std::string& peer_id,
896
- const std::vector<uint8_t>& data) {
897
- if (data.size() < 4 || std::memcmp(data.data(), CHUNK_MAGIC, 4) != 0) {
898
- return false; // not a file-transfer frame
899
- }
900
- const uint8_t* p = data.data();
901
- size_t n = data.size();
902
- size_t pos = 4;
903
-
904
- if (pos + 2 > n) return true;
905
- uint16_t id_len = get_u16(p + pos);
906
- pos += 2;
907
- if (pos + id_len > n) return true;
908
- std::string id(reinterpret_cast<const char*>(p + pos), id_len);
909
- pos += id_len;
910
- if (pos + 4 + 8 + 4 + 4 > n) return true;
911
- uint32_t file_index = get_u32(p + pos); pos += 4;
912
- uint64_t offset = get_u64(p + pos); pos += 8;
913
- uint32_t data_len = get_u32(p + pos); pos += 4;
914
- uint32_t crc = get_u32(p + pos); pos += 4;
915
- if (pos + data_len != n) {
916
- LOG_FT_WARN("Chunk frame from " << peer_id << " has inconsistent length");
917
- return true;
918
- }
919
- on_chunk(id, peer_id, file_index, offset, p + pos, data_len, crc);
920
- return true;
921
- }
922
-
923
- void FileTransferManager::on_chunk(const std::string& transfer_id, const std::string& peer_id,
924
- uint32_t file_index, uint64_t offset,
925
- const uint8_t* data, uint32_t len, uint32_t crc) {
926
- auto t = find(transfer_id);
927
- if (!t || t->direction != FileTransferDirection::RECEIVING) return;
928
- FileTransferConfig cfg = get_config();
929
-
930
- // Acknowledge at least twice per window so the sender never stalls waiting
931
- // for a progress update it will not receive.
932
- uint64_t ack_interval = std::min<uint64_t>(
933
- cfg.progress_interval, std::max<uint32_t>(1, cfg.window_bytes / 2));
934
-
935
- std::string temp_path;
936
- uint64_t file_size = 0;
937
- std::string fail_error;
938
- {
939
- std::lock_guard<std::mutex> lk(t->mtx);
940
- // Only accept chunks while the transfer is live (PAUSED still drains
941
- // chunks already in flight).
942
- if (t->status != FileTransferStatus::IN_PROGRESS &&
943
- t->status != FileTransferStatus::PAUSED) {
944
- return;
945
- }
946
- if (file_index >= t->files.size() || file_index != t->recv_file) {
947
- // Strict ordering is guaranteed by the reliable transport.
948
- fail_error = "out-of-order file index";
949
- } else {
950
- TransferFile& f = t->files[file_index];
951
- if (offset != f.received) {
952
- fail_error = "out-of-order chunk offset";
953
- } else if (offset + len > f.size) {
954
- fail_error = "chunk exceeds declared file size";
955
- } else {
956
- temp_path = f.temp_path;
957
- file_size = f.size;
958
- }
959
- }
960
- if (!fail_error.empty()) {
961
- t->status = FileTransferStatus::FAILED;
962
- t->error = fail_error;
963
- t->cv.notify_all();
964
- }
965
- }
966
- if (!fail_error.empty()) {
967
- nlohmann::json done{{"transfer_id", transfer_id}, {"success", false},
968
- {"error", fail_error}};
969
- client_.send(peer_id, MSG_COMPLETE, done);
970
- finish(t, false, fail_error);
971
- return;
972
- }
973
-
974
- if (cfg.verify_integrity && crc32(data, len) != crc) {
975
- nlohmann::json done{{"transfer_id", transfer_id}, {"success", false},
976
- {"error", "chunk CRC mismatch"}};
977
- client_.send(peer_id, MSG_COMPLETE, done);
978
- finish(t, false, "chunk CRC mismatch");
979
- return;
980
- }
981
-
982
- // Create the temp file on first contact, pre-sized to the declared length.
983
- {
984
- bool need_create;
985
- {
986
- std::lock_guard<std::mutex> lk(t->mtx);
987
- need_create = !t->files[file_index].temp_created;
988
- }
989
- if (need_create) {
990
- if (!create_file_with_size(temp_path.c_str(), file_size)) {
991
- nlohmann::json done{{"transfer_id", transfer_id}, {"success", false},
992
- {"error", "cannot create destination temp file"}};
993
- client_.send(peer_id, MSG_COMPLETE, done);
994
- finish(t, false, "cannot create temp file");
995
- return;
996
- }
997
- std::lock_guard<std::mutex> lk(t->mtx);
998
- t->files[file_index].temp_created = true;
999
- }
1000
- }
1001
-
1002
- if (!write_file_chunk(temp_path, offset, data, len)) {
1003
- nlohmann::json done{{"transfer_id", transfer_id}, {"success", false},
1004
- {"error", "receiver failed to write to disk"}};
1005
- client_.send(peer_id, MSG_COMPLETE, done);
1006
- finish(t, false, "failed to write chunk to disk");
1007
- return;
1008
- }
1009
-
1010
- bool data_complete = false;
1011
- bool send_ack = false;
1012
- uint64_t ack_bytes = 0;
1013
- {
1014
- std::lock_guard<std::mutex> lk(t->mtx);
1015
- TransferFile& f = t->files[file_index];
1016
- if (f.received == 0) sha256_reset(&t->recv_hash);
1017
- sha256_update(&t->recv_hash, data, len);
1018
- f.received += len;
1019
- t->bytes_done += len;
1020
- t->last_activity = std::chrono::steady_clock::now();
1021
-
1022
- if (f.received >= f.size) {
1023
- uint8_t digest[SHA256_HASH_SIZE];
1024
- sha256_finish(&t->recv_hash, digest);
1025
- f.computed_sha = to_hex(digest, SHA256_HASH_SIZE);
1026
- data_complete = true;
1027
- }
1028
- if (data_complete || t->bytes_done - t->last_ack_sent >= ack_interval) {
1029
- t->last_ack_sent = t->bytes_done;
1030
- ack_bytes = t->bytes_done;
1031
- send_ack = true;
1032
- }
1033
- }
1034
- {
1035
- std::lock_guard<std::mutex> lk(stats_mutex_);
1036
- stat_bytes_received_ += len;
1037
- }
1038
-
1039
- if (send_ack) {
1040
- nlohmann::json prog{{"transfer_id", transfer_id}, {"bytes_received", ack_bytes}};
1041
- client_.send(peer_id, MSG_PROGRESS, prog);
1042
- }
1043
- emit_progress(t);
1044
-
1045
- if (data_complete) try_finalize_file(t, file_index);
1046
- }
1047
-
1048
- void FileTransferManager::on_file_end(const std::string& /*peer_id*/, const nlohmann::json& msg) {
1049
- try {
1050
- std::string id = msg.at("transfer_id").get<std::string>();
1051
- auto t = find(id);
1052
- if (!t || t->direction != FileTransferDirection::RECEIVING) return;
1053
- size_t file_index = msg.at("file_index").get<size_t>();
1054
- std::string sha = msg.value("sha256", std::string());
1055
- {
1056
- std::lock_guard<std::mutex> lk(t->mtx);
1057
- if (t->is_terminal() || file_index >= t->files.size()) return;
1058
- TransferFile& f = t->files[file_index];
1059
- if (f.finalized) return;
1060
- f.expected_sha = sha;
1061
- f.sha_known = true;
1062
- // A zero-byte file produces no chunks, so hash it here.
1063
- if (f.size == 0 && f.computed_sha.empty()) f.computed_sha = sha256_of_empty();
1064
- }
1065
- try_finalize_file(t, file_index);
1066
- } catch (const std::exception& e) {
1067
- LOG_FT_ERROR("Malformed ft_file_end: " << e.what());
1068
- }
1069
- }
1070
-
1071
- // Moves a fully-received, verified file to its destination. When the last file
1072
- // of the transfer is finalized, completes the whole transfer.
1073
- void FileTransferManager::try_finalize_file(const std::shared_ptr<Transfer>& t, size_t file_index) {
1074
- FileTransferConfig cfg = get_config();
1075
- std::string final_path, temp_path, rel_path;
1076
- bool do_finalize = false;
1077
- bool sha_mismatch = false;
1078
- bool zero_byte = false;
1079
- {
1080
- std::lock_guard<std::mutex> lk(t->mtx);
1081
- if (t->is_terminal() || file_index >= t->files.size()) return;
1082
- TransferFile& f = t->files[file_index];
1083
- if (f.finalized) return;
1084
- if (f.received < f.size) return; // data not complete yet
1085
- if (!f.sha_known) return; // ft_file_end not received yet
1086
-
1087
- if (cfg.verify_integrity && f.computed_sha != f.expected_sha) {
1088
- sha_mismatch = true;
1089
- } else {
1090
- do_finalize = true;
1091
- final_path = f.final_path;
1092
- temp_path = f.temp_path;
1093
- rel_path = f.relative_path;
1094
- zero_byte = (f.size == 0);
1095
- }
1096
- }
1097
-
1098
- if (sha_mismatch) {
1099
- nlohmann::json done{{"transfer_id", t->id}, {"success", false},
1100
- {"error", "SHA-256 mismatch"}};
1101
- client_.send(t->peer_id, MSG_COMPLETE, done);
1102
- finish(t, false, "SHA-256 mismatch for " + rel_path);
1103
- return;
1104
- }
1105
- if (!do_finalize) return;
1106
-
1107
- // Place the file at its destination (parent directories created as needed).
1108
- std::string parent = get_parent_directory(final_path.c_str());
1109
- if (!parent.empty()) create_directories(parent.c_str());
1110
- if (file_exists(final_path)) delete_file(final_path.c_str());
1111
-
1112
- bool ok;
1113
- if (zero_byte) {
1114
- ok = create_file_with_size(final_path.c_str(), 0);
1115
- if (file_exists(temp_path)) delete_file(temp_path.c_str());
1116
- } else {
1117
- ok = rename_file(temp_path, final_path);
1118
- if (!ok) {
1119
- // rename fails across volumes; fall back to copy + delete.
1120
- ok = copy_file(temp_path.c_str(), final_path.c_str());
1121
- if (ok) delete_file(temp_path.c_str());
1122
- }
1123
- }
1124
- if (!ok) {
1125
- nlohmann::json done{{"transfer_id", t->id}, {"success", false},
1126
- {"error", "cannot write destination file"}};
1127
- client_.send(t->peer_id, MSG_COMPLETE, done);
1128
- finish(t, false, "cannot move file to " + final_path);
1129
- return;
1130
- }
1131
-
1132
- bool all_done = false;
1133
- {
1134
- std::lock_guard<std::mutex> lk(t->mtx);
1135
- TransferFile& f = t->files[file_index];
1136
- f.finalized = true;
1137
- t->files_done++;
1138
- if (file_index == t->recv_file) t->recv_file++;
1139
- all_done = true;
1140
- for (const auto& ff : t->files) {
1141
- if (!ff.finalized) { all_done = false; break; }
1142
- }
1143
- }
1144
- emit_progress(t);
1145
-
1146
- if (all_done) {
1147
- nlohmann::json done{{"transfer_id", t->id}, {"success", true}};
1148
- client_.send(t->peer_id, MSG_COMPLETE, done);
1149
- finish(t, true, "");
1150
- }
1151
- }
1152
-
1153
- // =============================================================================
1154
- // Control message handlers
1155
- // =============================================================================
1156
-
1157
- void FileTransferManager::on_response(const std::string& /*peer_id*/, const nlohmann::json& msg) {
1158
- try {
1159
- std::string id = msg.at("transfer_id").get<std::string>();
1160
- auto t = find(id);
1161
- if (!t || t->direction != FileTransferDirection::SENDING) return;
1162
- bool accepted = msg.value("accepted", false);
1163
-
1164
- if (accepted) {
1165
- {
1166
- std::lock_guard<std::mutex> lk(t->mtx);
1167
- if (t->status != FileTransferStatus::STARTING) return;
1168
- t->status = FileTransferStatus::IN_PROGRESS;
1169
- t->start_time = std::chrono::steady_clock::now();
1170
- t->last_activity = t->start_time;
1171
- t->rate_mark_time = t->start_time;
1172
- }
1173
- LOG_FT_INFO("Transfer " << id << " accepted by peer");
1174
- queue_send(id);
1175
- } else {
1176
- std::string reason = msg.value("reason", std::string("rejected by peer"));
1177
- finish(t, false, reason);
1178
- }
1179
- } catch (const std::exception& e) {
1180
- LOG_FT_ERROR("Malformed ft_response: " << e.what());
1181
- }
1182
- }
1183
-
1184
- void FileTransferManager::on_progress(const std::string& /*peer_id*/, const nlohmann::json& msg) {
1185
- try {
1186
- std::string id = msg.at("transfer_id").get<std::string>();
1187
- auto t = find(id);
1188
- if (!t || t->direction != FileTransferDirection::SENDING) return;
1189
- uint64_t acked = msg.value("bytes_received", uint64_t(0));
1190
- {
1191
- std::lock_guard<std::mutex> lk(t->mtx);
1192
- if (acked > t->acked_bytes) t->acked_bytes = acked;
1193
- t->last_activity = std::chrono::steady_clock::now();
1194
- t->cv.notify_all(); // wake the streaming worker if it was throttled
1195
- }
1196
- } catch (const std::exception& e) {
1197
- LOG_FT_ERROR("Malformed ft_progress: " << e.what());
1198
- }
1199
- }
1200
-
1201
- void FileTransferManager::on_complete(const std::string& /*peer_id*/, const nlohmann::json& msg) {
1202
- try {
1203
- std::string id = msg.at("transfer_id").get<std::string>();
1204
- auto t = find(id);
1205
- if (!t) return;
1206
- bool success = msg.value("success", false);
1207
- std::string error = msg.value("error", std::string());
1208
- finish(t, success, success ? "" : (error.empty() ? "peer reported failure" : error));
1209
- } catch (const std::exception& e) {
1210
- LOG_FT_ERROR("Malformed ft_complete: " << e.what());
1211
- }
1212
- }
1213
-
1214
- void FileTransferManager::on_control(const std::string& /*peer_id*/, const nlohmann::json& msg) {
1215
- try {
1216
- std::string id = msg.at("transfer_id").get<std::string>();
1217
- std::string action = msg.at("action").get<std::string>();
1218
- auto t = find(id);
1219
- if (!t) return;
1220
-
1221
- if (action == "pause") {
1222
- std::lock_guard<std::mutex> lk(t->mtx);
1223
- if (t->status == FileTransferStatus::IN_PROGRESS) {
1224
- t->status = FileTransferStatus::PAUSED;
1225
- t->cv.notify_all();
1226
- }
1227
- } else if (action == "resume") {
1228
- bool requeue = false;
1229
- {
1230
- std::lock_guard<std::mutex> lk(t->mtx);
1231
- if (t->status == FileTransferStatus::PAUSED) {
1232
- if (t->direction == FileTransferDirection::SENDING) {
1233
- t->status = FileTransferStatus::RESUMING;
1234
- requeue = true;
1235
- } else {
1236
- t->status = FileTransferStatus::IN_PROGRESS;
1237
- }
1238
- t->cv.notify_all();
1239
- }
1240
- }
1241
- if (requeue) queue_send(id);
1242
- } else if (action == "cancel") {
1243
- {
1244
- std::lock_guard<std::mutex> lk(t->mtx);
1245
- if (t->is_terminal()) return;
1246
- t->status = FileTransferStatus::CANCELLED;
1247
- t->cv.notify_all();
1248
- }
1249
- finish(t, false, "cancelled by peer");
1250
- }
1251
- } catch (const std::exception& e) {
1252
- LOG_FT_ERROR("Malformed ft_control: " << e.what());
1253
- }
1254
- }
1255
-
1256
- // =============================================================================
1257
- // Transfer control (local side)
1258
- // =============================================================================
1259
-
1260
- void FileTransferManager::send_control(const std::shared_ptr<Transfer>& t,
1261
- const std::string& action) {
1262
- nlohmann::json ctl{{"transfer_id", t->id}, {"action", action}};
1263
- client_.send(t->peer_id, MSG_CONTROL, ctl);
1264
- }
1265
-
1266
- bool FileTransferManager::pause(const std::string& transfer_id) {
1267
- auto t = find(transfer_id);
1268
- if (!t) return false;
1269
- {
1270
- std::lock_guard<std::mutex> lk(t->mtx);
1271
- if (t->status != FileTransferStatus::IN_PROGRESS) return false;
1272
- t->status = FileTransferStatus::PAUSED;
1273
- t->cv.notify_all();
1274
- }
1275
- send_control(t, "pause");
1276
- emit_progress(t);
1277
- LOG_FT_INFO("Transfer " << transfer_id << " paused");
1278
- return true;
1279
- }
1280
-
1281
- bool FileTransferManager::resume(const std::string& transfer_id) {
1282
- auto t = find(transfer_id);
1283
- if (!t) return false;
1284
- bool requeue = false;
1285
- {
1286
- std::lock_guard<std::mutex> lk(t->mtx);
1287
- if (t->status != FileTransferStatus::PAUSED) return false;
1288
- if (t->direction == FileTransferDirection::SENDING) {
1289
- t->status = FileTransferStatus::RESUMING;
1290
- requeue = true;
1291
- } else {
1292
- t->status = FileTransferStatus::IN_PROGRESS;
1293
- }
1294
- t->last_activity = std::chrono::steady_clock::now();
1295
- t->cv.notify_all();
1296
- }
1297
- send_control(t, "resume");
1298
- if (requeue) queue_send(transfer_id);
1299
- emit_progress(t);
1300
- LOG_FT_INFO("Transfer " << transfer_id << " resumed");
1301
- return true;
1302
- }
1303
-
1304
- bool FileTransferManager::cancel(const std::string& transfer_id) {
1305
- auto t = find(transfer_id);
1306
- if (!t) return false;
1307
- {
1308
- std::lock_guard<std::mutex> lk(t->mtx);
1309
- if (t->is_terminal()) return false;
1310
- t->status = FileTransferStatus::CANCELLED;
1311
- t->cv.notify_all();
1312
- }
1313
- send_control(t, "cancel");
1314
- finish(t, false, "cancelled");
1315
- LOG_FT_INFO("Transfer " << transfer_id << " cancelled");
1316
- return true;
1317
- }
1318
-
1319
- // =============================================================================
1320
- // Peer disconnect
1321
- // =============================================================================
1322
-
1323
- void FileTransferManager::on_peer_disconnected(const std::string& peer_id) {
1324
- std::vector<std::shared_ptr<Transfer>> affected;
1325
- {
1326
- std::lock_guard<std::mutex> lk(transfers_mutex_);
1327
- for (auto& kv : transfers_) {
1328
- if (kv.second->peer_id == peer_id) affected.push_back(kv.second);
1329
- }
1330
- }
1331
- for (auto& t : affected) {
1332
- bool active;
1333
- {
1334
- std::lock_guard<std::mutex> lk(t->mtx);
1335
- active = !t->is_terminal();
1336
- if (active) t->cv.notify_all();
1337
- }
1338
- if (active) finish(t, false, "peer disconnected");
1339
- }
1340
- }
1341
-
1342
- // =============================================================================
1343
- // Maintenance: timeouts and cleanup
1344
- // =============================================================================
1345
-
1346
- void FileTransferManager::maintenance_loop() {
1347
- while (running_.load()) {
1348
- {
1349
- std::unique_lock<std::mutex> lk(maintenance_mutex_);
1350
- maintenance_cv_.wait_for(lk, std::chrono::seconds(2),
1351
- [this] { return !running_.load(); });
1352
- }
1353
- if (!running_.load()) return;
1354
-
1355
- auto now = std::chrono::steady_clock::now();
1356
- uint32_t timeout_secs = get_config().transfer_timeout_secs;
1357
-
1358
- std::vector<std::shared_ptr<Transfer>> all;
1359
- {
1360
- std::lock_guard<std::mutex> lk(transfers_mutex_);
1361
- for (auto& kv : transfers_) all.push_back(kv.second);
1362
- }
1363
-
1364
- std::vector<std::shared_ptr<Transfer>> timed_out;
1365
- std::vector<std::string> to_purge;
1366
- for (auto& t : all) {
1367
- std::lock_guard<std::mutex> lk(t->mtx);
1368
- auto idle = std::chrono::duration_cast<std::chrono::seconds>(now - t->last_activity);
1369
- if (t->is_terminal()) {
1370
- if (idle > FINISHED_RETENTION) to_purge.push_back(t->id);
1371
- } else if (t->status != FileTransferStatus::PAUSED &&
1372
- idle.count() > timeout_secs) {
1373
- timed_out.push_back(t);
1374
- }
1375
- }
1376
-
1377
- for (auto& t : timed_out) {
1378
- LOG_FT_WARN("Transfer " << t->id << " timed out");
1379
- nlohmann::json done{{"transfer_id", t->id}, {"success", false},
1380
- {"error", "timed out"}};
1381
- client_.send(t->peer_id, MSG_COMPLETE, done);
1382
- finish(t, false, "timed out");
1383
- }
1384
- if (!to_purge.empty()) {
1385
- std::lock_guard<std::mutex> lk(transfers_mutex_);
1386
- for (auto& id : to_purge) transfers_.erase(id);
1387
- }
1388
- }
1389
- }
1390
-
1391
- // =============================================================================
1392
- // Utilities
1393
- // =============================================================================
1394
-
1395
- std::string FileTransferManager::compute_file_sha256(const std::string& path) {
1396
- int64_t size = get_file_size(path.c_str());
1397
- if (size < 0) return "";
1398
- sha256_context_t ctx;
1399
- sha256_reset(&ctx);
1400
- std::vector<uint8_t> buf(64 * 1024);
1401
- uint64_t offset = 0;
1402
- uint64_t remaining = static_cast<uint64_t>(size);
1403
- while (remaining > 0) {
1404
- uint32_t want = static_cast<uint32_t>(std::min<uint64_t>(buf.size(), remaining));
1405
- if (!read_file_chunk(path, offset, buf.data(), want)) return "";
1406
- sha256_update(&ctx, buf.data(), want);
1407
- offset += want;
1408
- remaining -= want;
1409
- }
1410
- uint8_t digest[SHA256_HASH_SIZE];
1411
- sha256_finish(&ctx, digest);
1412
- return to_hex(digest, SHA256_HASH_SIZE);
1413
- }
1414
-
1415
- } // namespace librats