librats 1.0.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.md +137 -332
  2. package/binding.gyp +16 -3
  3. package/lib/index.d.ts +271 -696
  4. package/lib/index.js +383 -44
  5. package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
  7. package/native-src/CMakeLists.txt +396 -179
  8. package/native-src/LICENSE +1 -1
  9. package/native-src/src/librats/bindings/rats.cpp +731 -0
  10. package/native-src/src/librats/bindings/rats.h +352 -0
  11. package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
  12. package/native-src/src/librats/bittorrent/bencode.h +176 -0
  13. package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
  14. package/native-src/src/librats/bittorrent/bitfield.h +76 -0
  15. package/native-src/src/librats/bittorrent/byte_io.h +58 -0
  16. package/native-src/src/librats/bittorrent/choker.cpp +25 -0
  17. package/native-src/src/librats/bittorrent/choker.h +46 -0
  18. package/native-src/src/librats/bittorrent/client.cpp +413 -0
  19. package/native-src/src/librats/bittorrent/client.h +227 -0
  20. package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
  21. package/native-src/src/librats/bittorrent/disk_io.h +150 -0
  22. package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
  23. package/native-src/src/librats/bittorrent/extensions.h +94 -0
  24. package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
  25. package/native-src/src/librats/bittorrent/file_storage.h +79 -0
  26. package/native-src/src/librats/bittorrent/log.h +42 -0
  27. package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
  28. package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
  29. package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
  30. package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
  31. package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
  32. package/native-src/src/librats/bittorrent/peer_list.h +75 -0
  33. package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
  34. package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
  35. package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
  36. package/native-src/src/librats/bittorrent/reactor.h +89 -0
  37. package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
  38. package/native-src/src/librats/bittorrent/resume_data.h +41 -0
  39. package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
  40. package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
  41. package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
  42. package/native-src/src/librats/bittorrent/torrent.h +260 -0
  43. package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
  44. package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
  45. package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
  46. package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
  47. package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
  48. package/native-src/src/librats/bittorrent/tracker.h +108 -0
  49. package/native-src/src/librats/bittorrent/types.cpp +206 -0
  50. package/native-src/src/librats/bittorrent/types.h +86 -0
  51. package/native-src/src/librats/core/address.cpp +35 -0
  52. package/native-src/src/librats/core/address.h +78 -0
  53. package/native-src/src/librats/core/bytes.h +69 -0
  54. package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
  55. package/native-src/src/librats/core/chained_send_buffer.h +183 -0
  56. package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
  57. package/native-src/src/librats/core/endpoint_parse.h +31 -0
  58. package/native-src/src/librats/core/event_bus.h +70 -0
  59. package/native-src/src/librats/core/host_endpoint.h +56 -0
  60. package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
  61. package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
  62. package/native-src/src/librats/core/ip_address.cpp +120 -0
  63. package/native-src/src/librats/core/ip_address.h +109 -0
  64. package/native-src/src/librats/core/mpsc_queue.h +47 -0
  65. package/native-src/src/librats/core/notifier.h +74 -0
  66. package/native-src/src/librats/core/receive_buffer.cpp +219 -0
  67. package/native-src/src/librats/core/receive_buffer.h +171 -0
  68. package/native-src/src/librats/core/service_registry.h +58 -0
  69. package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
  70. package/native-src/src/librats/core/socket.h +496 -0
  71. package/native-src/src/librats/core/timer_queue.h +105 -0
  72. package/native-src/src/librats/core/types.cpp +42 -0
  73. package/native-src/src/librats/core/types.h +93 -0
  74. package/native-src/src/librats/core/wakeup_pipe.h +83 -0
  75. package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
  76. package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
  77. package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
  78. package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
  79. package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
  80. package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
  81. package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
  82. package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
  83. package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
  84. package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
  85. package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
  86. package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
  87. package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
  88. package/native-src/src/librats/crypto/hkdf.c +266 -0
  89. package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
  90. package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
  91. package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
  92. package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
  93. package/native-src/src/librats/crypto/poly1305.h +37 -0
  94. package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
  95. package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
  96. package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
  97. package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
  98. package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
  99. package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
  100. package/native-src/src/librats/dht/announce.cpp +37 -0
  101. package/native-src/src/librats/dht/announce.h +41 -0
  102. package/native-src/src/librats/dht/bep42.cpp +109 -0
  103. package/native-src/src/librats/dht/bep42.h +48 -0
  104. package/native-src/src/librats/dht/dht.cpp +501 -0
  105. package/native-src/src/librats/dht/dht.h +119 -0
  106. package/native-src/src/librats/dht/dht_runner.cpp +103 -0
  107. package/native-src/src/librats/dht/dht_runner.h +71 -0
  108. package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
  109. package/native-src/src/librats/dht/dos_blocker.h +47 -0
  110. package/native-src/src/librats/dht/find_peers.cpp +52 -0
  111. package/native-src/src/librats/dht/find_peers.h +73 -0
  112. package/native-src/src/librats/dht/id.h +167 -0
  113. package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
  114. package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
  115. package/native-src/src/librats/dht/log.h +38 -0
  116. package/native-src/src/librats/dht/node.cpp +473 -0
  117. package/native-src/src/librats/dht/node.h +164 -0
  118. package/native-src/src/librats/dht/node_entry.h +81 -0
  119. package/native-src/src/librats/dht/observer.h +72 -0
  120. package/native-src/src/librats/dht/persistence.cpp +90 -0
  121. package/native-src/src/librats/dht/persistence.h +32 -0
  122. package/native-src/src/librats/dht/routing_table.cpp +559 -0
  123. package/native-src/src/librats/dht/routing_table.h +185 -0
  124. package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
  125. package/native-src/src/librats/dht/rpc_manager.h +77 -0
  126. package/native-src/src/librats/dht/storage.cpp +92 -0
  127. package/native-src/src/librats/dht/storage.h +74 -0
  128. package/native-src/src/librats/dht/transport.h +27 -0
  129. package/native-src/src/librats/dht/traversal.cpp +326 -0
  130. package/native-src/src/librats/dht/traversal.h +120 -0
  131. package/native-src/src/librats/dht/udp_transport.cpp +49 -0
  132. package/native-src/src/librats/dht/udp_transport.h +51 -0
  133. package/native-src/src/librats/mdns/log.h +22 -0
  134. package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
  135. package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
  136. package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
  137. package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
  138. package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
  139. package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
  140. package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
  141. package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
  142. package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
  143. package/native-src/src/librats/node/config.h +110 -0
  144. package/native-src/src/librats/node/dial_service.h +54 -0
  145. package/native-src/src/librats/node/dialer.cpp +264 -0
  146. package/native-src/src/librats/node/dialer.h +188 -0
  147. package/native-src/src/librats/node/host_events.h +26 -0
  148. package/native-src/src/librats/node/identify.cpp +130 -0
  149. package/native-src/src/librats/node/identify.h +71 -0
  150. package/native-src/src/librats/node/nat_status.cpp +103 -0
  151. package/native-src/src/librats/node/nat_status.h +118 -0
  152. package/native-src/src/librats/node/node.cpp +826 -0
  153. package/native-src/src/librats/node/node.h +333 -0
  154. package/native-src/src/librats/node/node_context.h +33 -0
  155. package/native-src/src/librats/node/peer_network.h +91 -0
  156. package/native-src/src/librats/peer/peer.h +49 -0
  157. package/native-src/src/librats/peer/peer_book.cpp +181 -0
  158. package/native-src/src/librats/peer/peer_book.h +88 -0
  159. package/native-src/src/librats/peer/peer_id.cpp +72 -0
  160. package/native-src/src/librats/peer/peer_id.h +62 -0
  161. package/native-src/src/librats/peer/peer_info.h +37 -0
  162. package/native-src/src/librats/peer/peer_table.cpp +137 -0
  163. package/native-src/src/librats/peer/peer_table.h +148 -0
  164. package/native-src/src/librats/security/handshaker.h +66 -0
  165. package/native-src/src/librats/security/identity.h +43 -0
  166. package/native-src/src/librats/security/noise_security.cpp +122 -0
  167. package/native-src/src/librats/security/noise_security.h +37 -0
  168. package/native-src/src/librats/security/plaintext_security.h +106 -0
  169. package/native-src/src/librats/security/session.h +37 -0
  170. package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
  171. package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
  172. package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
  173. package/native-src/src/librats/subsystems/bittorrent.h +136 -0
  174. package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
  175. package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
  176. package/native-src/src/librats/subsystems/dht_service.h +36 -0
  177. package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
  178. package/native-src/src/librats/subsystems/file_transfer.h +367 -0
  179. package/native-src/src/librats/subsystems/hole_punch.cpp +532 -0
  180. package/native-src/src/librats/subsystems/hole_punch.h +266 -0
  181. package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
  182. package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
  183. package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
  184. package/native-src/src/librats/subsystems/message_json.cpp +112 -0
  185. package/native-src/src/librats/subsystems/message_json.h +88 -0
  186. package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
  187. package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
  188. package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
  189. package/native-src/src/librats/subsystems/ping_service.h +66 -0
  190. package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
  191. package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
  192. package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
  193. package/native-src/src/librats/subsystems/pubsub.h +175 -0
  194. package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
  195. package/native-src/src/librats/subsystems/reconnection.h +126 -0
  196. package/native-src/src/librats/transport/connection.cpp +343 -0
  197. package/native-src/src/librats/transport/connection.h +283 -0
  198. package/native-src/src/librats/transport/link.h +96 -0
  199. package/native-src/src/librats/transport/reactor.cpp +540 -0
  200. package/native-src/src/librats/transport/reactor.h +224 -0
  201. package/native-src/src/librats/transport/reactor_pool.h +81 -0
  202. package/native-src/src/librats/transport/tcp_link.cpp +49 -0
  203. package/native-src/src/librats/transport/tcp_link.h +43 -0
  204. package/native-src/src/librats/transport/udp_mux.cpp +617 -0
  205. package/native-src/src/librats/transport/udp_mux.h +363 -0
  206. package/native-src/src/librats/transport/udp_packet.cpp +121 -0
  207. package/native-src/src/librats/transport/udp_packet.h +190 -0
  208. package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
  209. package/native-src/src/librats/transport/udp_stream.h +614 -0
  210. package/native-src/src/librats/util/features.h.in +51 -0
  211. package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
  212. package/native-src/src/librats/util/fs.h +136 -0
  213. package/native-src/src/librats/util/json.cpp +1002 -0
  214. package/native-src/src/librats/util/json.h +444 -0
  215. package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
  216. package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
  217. package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
  218. package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
  219. package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
  220. package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
  221. package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
  222. package/native-src/src/librats/util/rats_export.h +69 -0
  223. package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
  224. package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
  225. package/native-src/src/librats/wire/frame.cpp +76 -0
  226. package/native-src/src/librats/wire/frame.h +110 -0
  227. package/native-src/src/librats/wire/message_router.cpp +45 -0
  228. package/native-src/src/librats/wire/message_router.h +47 -0
  229. package/package.json +5 -4
  230. package/scripts/build-librats.js +1 -0
  231. package/scripts/postinstall.js +3 -3
  232. package/scripts/prepare-package.js +4 -4
  233. package/scripts/verify-installation.js +63 -105
  234. package/src/librats_node.cpp +1042 -1324
  235. package/native-src/src/bencode.cpp +0 -485
  236. package/native-src/src/bencode.h +0 -145
  237. package/native-src/src/bittorrent.cpp +0 -14
  238. package/native-src/src/bittorrent.h +0 -74
  239. package/native-src/src/bt_bitfield.cpp +0 -372
  240. package/native-src/src/bt_bitfield.h +0 -316
  241. package/native-src/src/bt_choker.cpp +0 -228
  242. package/native-src/src/bt_choker.h +0 -147
  243. package/native-src/src/bt_client.cpp +0 -1047
  244. package/native-src/src/bt_client.h +0 -445
  245. package/native-src/src/bt_create_torrent.cpp +0 -677
  246. package/native-src/src/bt_create_torrent.h +0 -473
  247. package/native-src/src/bt_extension.cpp +0 -469
  248. package/native-src/src/bt_extension.h +0 -309
  249. package/native-src/src/bt_file_storage.cpp +0 -261
  250. package/native-src/src/bt_file_storage.h +0 -298
  251. package/native-src/src/bt_handshake.cpp +0 -134
  252. package/native-src/src/bt_handshake.h +0 -157
  253. package/native-src/src/bt_messages.cpp +0 -364
  254. package/native-src/src/bt_messages.h +0 -324
  255. package/native-src/src/bt_network.cpp +0 -1007
  256. package/native-src/src/bt_network.h +0 -417
  257. package/native-src/src/bt_peer_connection.cpp +0 -742
  258. package/native-src/src/bt_peer_connection.h +0 -592
  259. package/native-src/src/bt_piece_picker.cpp +0 -786
  260. package/native-src/src/bt_piece_picker.h +0 -473
  261. package/native-src/src/bt_resume_data.cpp +0 -410
  262. package/native-src/src/bt_resume_data.h +0 -249
  263. package/native-src/src/bt_torrent.cpp +0 -2120
  264. package/native-src/src/bt_torrent.h +0 -641
  265. package/native-src/src/bt_torrent_info.cpp +0 -659
  266. package/native-src/src/bt_torrent_info.h +0 -418
  267. package/native-src/src/bt_types.h +0 -621
  268. package/native-src/src/chained_send_buffer.cpp +0 -75
  269. package/native-src/src/chained_send_buffer.h +0 -137
  270. package/native-src/src/crypto/hkdf.c +0 -266
  271. package/native-src/src/crypto/poly1305.h +0 -36
  272. package/native-src/src/dht.cpp +0 -3311
  273. package/native-src/src/dht.h +0 -717
  274. package/native-src/src/disk_io.cpp +0 -632
  275. package/native-src/src/disk_io.h +0 -315
  276. package/native-src/src/file_transfer.cpp +0 -1415
  277. package/native-src/src/file_transfer.h +0 -286
  278. package/native-src/src/fs.h +0 -108
  279. package/native-src/src/gossipsub.cpp +0 -1139
  280. package/native-src/src/gossipsub.h +0 -403
  281. package/native-src/src/ice.cpp +0 -893
  282. package/native-src/src/ice.h +0 -559
  283. package/native-src/src/json.hpp +0 -25526
  284. package/native-src/src/librats.cpp +0 -2378
  285. package/native-src/src/librats.h +0 -2324
  286. package/native-src/src/librats_bittorrent.cpp +0 -601
  287. package/native-src/src/librats_c.cpp +0 -1557
  288. package/native-src/src/librats_c.h +0 -323
  289. package/native-src/src/librats_discovery.cpp +0 -402
  290. package/native-src/src/librats_encryption.cpp +0 -275
  291. package/native-src/src/librats_file_transfer.cpp +0 -144
  292. package/native-src/src/librats_gossipsub.cpp +0 -289
  293. package/native-src/src/librats_ice.cpp +0 -213
  294. package/native-src/src/librats_log_macros.h +0 -36
  295. package/native-src/src/librats_logging.cpp +0 -173
  296. package/native-src/src/librats_mdns.cpp +0 -166
  297. package/native-src/src/librats_persistence.cpp +0 -796
  298. package/native-src/src/librats_portmap.cpp +0 -419
  299. package/native-src/src/librats_reconnection.cpp +0 -218
  300. package/native-src/src/librats_statistic.cpp +0 -105
  301. package/native-src/src/librats_storage.cpp +0 -189
  302. package/native-src/src/rats_export.h +0 -17
  303. package/native-src/src/receive_buffer.cpp +0 -82
  304. package/native-src/src/receive_buffer.h +0 -127
  305. package/native-src/src/socket.h +0 -228
  306. package/native-src/src/threadmanager.cpp +0 -105
  307. package/native-src/src/threadmanager.h +0 -53
  308. package/native-src/src/tracker.cpp +0 -1264
  309. package/native-src/src/tracker.h +0 -319
  310. package/native-src/src/turn.cpp +0 -762
  311. package/native-src/src/turn.h +0 -460
  312. package/native-src/src/wakeup_pipe.h +0 -60
  313. /package/native-src/src/{os.h → librats/util/os.h} +0 -0
@@ -0,0 +1,972 @@
1
+ #include "librats/subsystems/file_transfer.h"
2
+ #include "librats/node/node_context.h"
3
+ #include "librats/util/fs.h"
4
+ #include "librats/util/logger.h"
5
+
6
+ #include <algorithm>
7
+ #include <cstring>
8
+ #include <vector>
9
+
10
+ namespace librats {
11
+
12
+ namespace {
13
+
14
+ enum : uint8_t {
15
+ OP_OFFER = 1, OP_RESPONSE = 2, OP_CHUNK = 3, OP_FILE_END = 4,
16
+ OP_PROGRESS = 5, OP_COMPLETE = 6, OP_CANCEL = 7, OP_PAUSE = 8, OP_RESUME = 9,
17
+ };
18
+
19
+ void put_u16(Bytes& b, uint16_t v) { b.push_back(v >> 8); b.push_back(v & 0xFF); }
20
+ void put_u32(Bytes& b, uint32_t v) { for (int i = 3; i >= 0; --i) b.push_back((v >> (i * 8)) & 0xFF); }
21
+ void put_u64(Bytes& b, uint64_t v) { for (int i = 7; i >= 0; --i) b.push_back((v >> (i * 8)) & 0xFF); }
22
+
23
+ struct Reader {
24
+ const uint8_t* p;
25
+ const uint8_t* end;
26
+ bool ok = true;
27
+ uint8_t u8() { if (p >= end) { ok = false; return 0; } return *p++; }
28
+ uint16_t u16() { if (end - p < 2) { ok = false; return 0; } uint16_t v = (uint16_t(p[0]) << 8) | p[1]; p += 2; return v; }
29
+ uint32_t u32() { if (end - p < 4) { ok = false; return 0; } uint32_t v = 0; for (int i = 0; i < 4; ++i) v = (v << 8) | *p++; return v; }
30
+ uint64_t u64() { if (end - p < 8) { ok = false; return 0; } uint64_t v = 0; for (int i = 0; i < 8; ++i) v = (v << 8) | *p++; return v; }
31
+ ByteView bytes(size_t n) { if (size_t(end - p) < n) { ok = false; return {}; } ByteView v(p, n); p += n; return v; }
32
+ ByteView rest() { ByteView v(p, size_t(end - p)); p = end; return v; }
33
+ size_t remaining() const { return size_t(end - p); }
34
+ };
35
+
36
+ // Smallest possible OFFER manifest entry on the wire: [path_len:u16][size:u64]
37
+ // with a zero-length path.
38
+ constexpr uint64_t kMinManifestEntry = 2 + 8;
39
+
40
+ // Recursively collect regular files under abs_dir, recording POSIX relative paths.
41
+ void scan_directory(const std::string& abs_dir, const std::string& rel_prefix,
42
+ std::vector<FileTransfer::FileEntry>& files, std::vector<std::string>& sources) {
43
+ std::vector<DirectoryEntry> entries;
44
+ if (!list_directory(abs_dir.c_str(), entries)) return;
45
+ for (const auto& e : entries) {
46
+ const std::string rel = rel_prefix.empty() ? e.name : rel_prefix + "/" + e.name;
47
+ if (e.is_directory) {
48
+ scan_directory(e.path, rel, files, sources);
49
+ } else {
50
+ const int64_t sz = get_file_size(e.path.c_str());
51
+ files.push_back({rel, sz > 0 ? static_cast<uint64_t>(sz) : 0});
52
+ sources.push_back(e.path);
53
+ }
54
+ }
55
+ }
56
+
57
+ } // namespace
58
+
59
+ // A relative path coming from a peer must not escape the destination directory.
60
+ bool FileTransfer::is_safe_relative_path(const std::string& p) {
61
+ if (p.empty()) return false;
62
+ if (p.front() == '/' || p.front() == '\\') return false;
63
+ if (p.size() >= 2 && p[1] == ':') return false; // Windows drive letter
64
+ size_t start = 0;
65
+ for (size_t i = 0; i <= p.size(); ++i) {
66
+ if (i == p.size() || p[i] == '/' || p[i] == '\\') {
67
+ std::string comp = p.substr(start, i - start);
68
+ if (comp.empty() || comp == "." || comp == "..") return false;
69
+ start = i + 1;
70
+ }
71
+ }
72
+ return true;
73
+ }
74
+
75
+ FileTransfer::FileTransfer(std::string temp_dir) { config_.temp_directory = std::move(temp_dir); }
76
+ FileTransfer::FileTransfer(Config config) : config_(std::move(config)) {}
77
+ FileTransfer::~FileTransfer() { stop(); }
78
+
79
+ void FileTransfer::attach(NodeContext& ctx) {
80
+ network_ = &ctx.network;
81
+ network_->on(MessageType::FileChunk,
82
+ [this](const Peer& peer, ByteView payload) { on_message(peer, payload); });
83
+ network_->on_peer_disconnected([this](const PeerId& id) {
84
+ // Fail every in-flight transfer with the departed peer and reclaim temps.
85
+ std::vector<std::shared_ptr<Outgoing>> out;
86
+ std::vector<std::shared_ptr<Incoming>> in;
87
+ {
88
+ std::lock_guard<std::mutex> lock(mutex_);
89
+ for (auto& [tid, t] : outgoing_) if (t->peer == id) out.push_back(t);
90
+ auto pit = incoming_.find(id);
91
+ if (pit != incoming_.end())
92
+ for (auto& [tid, t] : pit->second) in.push_back(t);
93
+ }
94
+ for (auto& t : out) finish_outgoing(t, false);
95
+ for (auto& t : in) finish_incoming(t, false, "peer disconnected");
96
+ });
97
+ }
98
+
99
+ void FileTransfer::start() {
100
+ if (running_.exchange(true)) return;
101
+ const uint32_t n = std::max<uint32_t>(1, config_.worker_threads);
102
+ for (uint32_t i = 0; i < n; ++i) workers_.emplace_back(&FileTransfer::worker_loop, this);
103
+ const uint32_t d = std::max<uint32_t>(1, config_.disk_threads);
104
+ for (uint32_t i = 0; i < d; ++i) disk_workers_.emplace_back(&FileTransfer::disk_worker_loop, this);
105
+ maintenance_thread_ = std::thread(&FileTransfer::maintenance_loop, this);
106
+ }
107
+
108
+ void FileTransfer::stop() {
109
+ if (!running_.exchange(false)) return;
110
+
111
+ queue_cv_.notify_all();
112
+ maintenance_cv_.notify_all();
113
+ disk_cv_.notify_all();
114
+
115
+ // Wake any worker blocked on a transfer's window/wait so run_send returns.
116
+ {
117
+ std::lock_guard<std::mutex> lock(mutex_);
118
+ for (auto& [id, t] : outgoing_) { std::lock_guard<std::mutex> lk(t->mtx); t->status = Status::Cancelled; t->cv.notify_all(); }
119
+ }
120
+ for (auto& w : workers_) if (w.joinable()) w.join();
121
+ workers_.clear();
122
+ // Join the disk pool before dropping any Incoming, so no writer touches a temp
123
+ // file while ~Incoming reclaims it.
124
+ for (auto& w : disk_workers_) if (w.joinable()) w.join();
125
+ disk_workers_.clear();
126
+ if (maintenance_thread_.joinable()) maintenance_thread_.join();
127
+
128
+ // Drop remaining transfers. ~Incoming closes any open handle and reclaims
129
+ // un-finalized temp files.
130
+ std::unordered_map<PeerId, std::unordered_map<uint64_t, std::shared_ptr<Incoming>>, PeerId::Hash> in;
131
+ {
132
+ std::lock_guard<std::mutex> lock(mutex_);
133
+ outgoing_.clear();
134
+ in.swap(incoming_);
135
+ }
136
+ in.clear();
137
+ }
138
+
139
+ FileTransfer::Stats FileTransfer::stats() const {
140
+ std::lock_guard<std::mutex> lock(stats_mutex_);
141
+ return stats_;
142
+ }
143
+
144
+ // ── lookups ───────────────────────────────────────────────────────────────────
145
+
146
+ std::shared_ptr<FileTransfer::Outgoing> FileTransfer::find_outgoing(uint64_t id) const {
147
+ std::lock_guard<std::mutex> lock(mutex_);
148
+ auto it = outgoing_.find(id);
149
+ return it == outgoing_.end() ? nullptr : it->second;
150
+ }
151
+
152
+ std::shared_ptr<FileTransfer::Incoming> FileTransfer::find_incoming(const PeerId& peer, uint64_t id) const {
153
+ std::lock_guard<std::mutex> lock(mutex_);
154
+ auto pit = incoming_.find(peer);
155
+ if (pit == incoming_.end()) return nullptr;
156
+ auto it = pit->second.find(id);
157
+ return it == pit->second.end() ? nullptr : it->second;
158
+ }
159
+
160
+ // ── small senders ─────────────────────────────────────────────────────────────
161
+
162
+ void FileTransfer::send_simple(const PeerId& peer, uint8_t op, uint64_t id) {
163
+ Bytes m; m.push_back(op); put_u64(m, id); send_to(peer, m);
164
+ }
165
+ void FileTransfer::send_complete(const PeerId& peer, uint64_t id, bool ok) {
166
+ Bytes m; m.push_back(OP_COMPLETE); put_u64(m, id); m.push_back(ok ? 1 : 0); send_to(peer, m);
167
+ }
168
+
169
+ // ── Sender: build + offer ───────────────────────────────────────────────────
170
+
171
+ uint64_t FileTransfer::send_file(const PeerId& to, const std::string& path) {
172
+ if (!is_file(path.c_str())) { LOG_WARN("filexfer", "send_file: not a file: " << path); return 0; }
173
+ const int64_t size = get_file_size(path.c_str());
174
+ if (size < 0) { LOG_WARN("filexfer", "send_file: cannot stat " << path); return 0; }
175
+
176
+ auto t = std::make_shared<Outgoing>();
177
+ t->peer = to;
178
+ t->name = get_filename_from_path(path.c_str());
179
+ t->root = path;
180
+ t->is_directory = false;
181
+ t->files.push_back({t->name, static_cast<uint64_t>(size)});
182
+ t->sources.push_back(path);
183
+ t->total_bytes = static_cast<uint64_t>(size);
184
+ return start_send(std::move(t));
185
+ }
186
+
187
+ uint64_t FileTransfer::send_directory(const PeerId& to, const std::string& dir_path) {
188
+ if (!is_directory(dir_path.c_str())) { LOG_WARN("filexfer", "send_directory: not a directory: " << dir_path); return 0; }
189
+
190
+ auto t = std::make_shared<Outgoing>();
191
+ t->name = get_filename_from_path(dir_path.c_str());
192
+ t->root = dir_path;
193
+ t->is_directory = true;
194
+ scan_directory(dir_path, "", t->files, t->sources);
195
+ for (const auto& f : t->files) t->total_bytes += f.size;
196
+ t->peer = to;
197
+ return start_send(std::move(t));
198
+ }
199
+
200
+ uint64_t FileTransfer::start_send(std::shared_ptr<Outgoing> t) {
201
+ const uint64_t id = next_id_.fetch_add(1);
202
+ t->id = id;
203
+ t->last_activity = std::chrono::steady_clock::now();
204
+ t->status = Status::Pending;
205
+ {
206
+ std::lock_guard<std::mutex> lock(mutex_);
207
+ outgoing_.emplace(id, t);
208
+ }
209
+
210
+ Bytes m;
211
+ m.push_back(OP_OFFER);
212
+ put_u64(m, id);
213
+ m.push_back(t->is_directory ? 1 : 0);
214
+ put_u64(m, t->total_bytes);
215
+ put_u16(m, static_cast<uint16_t>(t->name.size()));
216
+ m.insert(m.end(), t->name.begin(), t->name.end());
217
+ put_u32(m, static_cast<uint32_t>(t->files.size()));
218
+ for (const auto& f : t->files) {
219
+ put_u16(m, static_cast<uint16_t>(f.relative_path.size()));
220
+ m.insert(m.end(), f.relative_path.begin(), f.relative_path.end());
221
+ put_u64(m, f.size);
222
+ }
223
+ send_to(t->peer, m);
224
+ LOG_INFO("filexfer", "Offering " << (t->is_directory ? "directory '" : "file '") << t->name
225
+ << "' (" << t->files.size() << " file(s), " << t->total_bytes << " B) [" << id << "]");
226
+ return id;
227
+ }
228
+
229
+ void FileTransfer::queue_send(uint64_t id) {
230
+ { std::lock_guard<std::mutex> lk(queue_mutex_); send_queue_.push(id); }
231
+ queue_cv_.notify_one();
232
+ }
233
+
234
+ void FileTransfer::worker_loop() {
235
+ while (running_.load()) {
236
+ uint64_t id;
237
+ {
238
+ std::unique_lock<std::mutex> lk(queue_mutex_);
239
+ queue_cv_.wait(lk, [this] { return !running_.load() || !send_queue_.empty(); });
240
+ if (!running_.load()) return;
241
+ id = send_queue_.front();
242
+ send_queue_.pop();
243
+ }
244
+ auto t = find_outgoing(id);
245
+ if (!t) continue;
246
+ {
247
+ std::lock_guard<std::mutex> lk(t->mtx);
248
+ if (t->worker_active) continue; // another worker owns it
249
+ t->worker_active = true;
250
+ }
251
+ run_send(t);
252
+ { std::lock_guard<std::mutex> lk(t->mtx); t->worker_active = false; }
253
+ }
254
+ }
255
+
256
+ void FileTransfer::run_send(const std::shared_ptr<Outgoing>& t) {
257
+ // One send buffer reused for every chunk: the fixed CHUNK header is written in
258
+ // place and the file data is read straight into the bytes that follow it, so a
259
+ // chunk's payload is never copied between the disk read and the wire (the only
260
+ // remaining copies are the transport's own framing + encryption). Reusing the
261
+ // buffer also drops the per-chunk allocation.
262
+ constexpr size_t kChunkHeader = 1 + 8 + 4 + 8; // op + id + file_index + offset
263
+ Bytes m;
264
+ m.reserve(kChunkHeader + config_.chunk_size);
265
+
266
+ // The source file is held open across all of its chunks (one open()/close()
267
+ // per file instead of one per chunk) and read sequentially straight into the
268
+ // reused send buffer.
269
+ FileStream fp;
270
+ size_t open_idx = SIZE_MAX;
271
+
272
+ while (running_.load()) {
273
+ size_t file_index;
274
+ uint64_t offset, file_size;
275
+ std::string source;
276
+ {
277
+ std::unique_lock<std::mutex> lk(t->mtx);
278
+ if (t->status != Status::Active) return; // paused / cancelled / done
279
+ if (t->cur_file >= t->files.size()) break; // all data streamed
280
+ file_index = t->cur_file;
281
+ offset = t->cur_offset;
282
+ file_size = t->files[file_index].size;
283
+ source = t->sources[file_index];
284
+ if (offset == 0) rats_sha256_reset(&t->hash);
285
+ }
286
+
287
+ // Empty file: no chunks, just the per-file SHA (of the empty input).
288
+ if (file_size == 0) {
289
+ rats_sha256_context_t e; rats_sha256_reset(&e);
290
+ uint8_t digest[RATS_SHA256_HASH_SIZE]; rats_sha256_finish(&e, digest);
291
+ Bytes fe; fe.push_back(OP_FILE_END); put_u64(fe, t->id);
292
+ put_u32(fe, static_cast<uint32_t>(file_index));
293
+ fe.insert(fe.end(), digest, digest + RATS_SHA256_HASH_SIZE);
294
+ send_to(t->peer, fe);
295
+ fp.close(); open_idx = SIZE_MAX;
296
+ std::lock_guard<std::mutex> lk(t->mtx);
297
+ t->cur_file++; t->cur_offset = 0; t->files_done++;
298
+ continue;
299
+ }
300
+
301
+ // (Re)open the source when we start a new file, seeking to the resume offset.
302
+ if (open_idx != file_index) {
303
+ fp.close();
304
+ if (!fp.open_read(source.c_str()) || !fp.seek(offset)) {
305
+ LOG_ERROR("filexfer", "open/seek error on " << source << " at " << offset);
306
+ send_complete(t->peer, t->id, false);
307
+ finish_outgoing(t, false);
308
+ return;
309
+ }
310
+ open_idx = file_index;
311
+ }
312
+
313
+ const uint32_t want = static_cast<uint32_t>(std::min<uint64_t>(config_.chunk_size, file_size - offset));
314
+ m.clear();
315
+ m.push_back(OP_CHUNK);
316
+ put_u64(m, t->id);
317
+ put_u32(m, static_cast<uint32_t>(file_index));
318
+ put_u64(m, offset);
319
+ m.resize(kChunkHeader + want); // no realloc: capacity was reserved up front
320
+ if (fp.read(m.data() + kChunkHeader, want) != want) {
321
+ LOG_ERROR("filexfer", "read error on " << source << " at " << offset);
322
+ send_complete(t->peer, t->id, false);
323
+ finish_outgoing(t, false);
324
+ return;
325
+ }
326
+ send_to(t->peer, m);
327
+
328
+ bool file_done = false;
329
+ uint8_t digest[RATS_SHA256_HASH_SIZE];
330
+ {
331
+ std::lock_guard<std::mutex> lk(t->mtx);
332
+ rats_sha256_update(&t->hash, m.data() + kChunkHeader, want);
333
+ t->cur_offset += want;
334
+ t->bytes_done += want;
335
+ t->last_activity = std::chrono::steady_clock::now();
336
+ if (t->cur_offset >= file_size) {
337
+ rats_sha256_finish(&t->hash, digest);
338
+ t->cur_file++; t->cur_offset = 0; t->files_done++;
339
+ file_done = true;
340
+ }
341
+ }
342
+ { std::lock_guard<std::mutex> lk(stats_mutex_); stats_.bytes_sent += want; }
343
+
344
+ if (file_done) {
345
+ Bytes e; e.push_back(OP_FILE_END); put_u64(e, t->id);
346
+ put_u32(e, static_cast<uint32_t>(file_index));
347
+ e.insert(e.end(), digest, digest + RATS_SHA256_HASH_SIZE);
348
+ send_to(t->peer, e);
349
+ }
350
+ emit_progress(t);
351
+
352
+ // Backpressure: stay within `window_bytes` of the receiver's last ack.
353
+ std::unique_lock<std::mutex> lk(t->mtx);
354
+ while (running_.load() && t->status == Status::Active &&
355
+ t->bytes_done - t->acked >= config_.window_bytes) {
356
+ t->cv.wait_for(lk, std::chrono::milliseconds(200));
357
+ }
358
+ }
359
+ // All data streamed; the transfer stays Active until the receiver's COMPLETE
360
+ // arrives (handled on the reactor thread), which calls finish_outgoing().
361
+ }
362
+
363
+ void FileTransfer::finish_outgoing(const std::shared_ptr<Outgoing>& t, bool success) {
364
+ bool cancelled;
365
+ {
366
+ std::lock_guard<std::mutex> lk(t->mtx);
367
+ if (t->finished) return;
368
+ t->finished = true;
369
+ cancelled = (t->status == Status::Cancelled);
370
+ if (!cancelled)
371
+ t->status = success ? Status::Completed : Status::Failed;
372
+ t->cv.notify_all();
373
+ }
374
+ {
375
+ std::lock_guard<std::mutex> lock(mutex_);
376
+ outgoing_.erase(t->id);
377
+ }
378
+ { std::lock_guard<std::mutex> lk(stats_mutex_); success ? ++stats_.completed : ++stats_.failed; }
379
+ emit_progress(t);
380
+ if (cancelled) LOG_INFO("filexfer", "Send [" << t->id << "] cancelled");
381
+ else if (success) LOG_INFO("filexfer", "Send [" << t->id << "] completed (" << t->root << ")");
382
+ else LOG_WARN("filexfer", "Send [" << t->id << "] failed");
383
+ if (complete_handler_) complete_handler_(t->id, success, t->root);
384
+ }
385
+
386
+ // ── Receiver ──────────────────────────────────────────────────────────────────
387
+
388
+ void FileTransfer::accept(const PeerId& from, uint64_t id, const std::string& dest_path) {
389
+ auto t = find_incoming(from, id);
390
+ if (!t) return;
391
+ bool empty_transfer = false;
392
+ {
393
+ std::lock_guard<std::mutex> lk(t->mtx);
394
+ if (t->status != Status::Pending) return;
395
+ t->dest_root = dest_path;
396
+ for (size_t i = 0; i < t->files.size(); ++i) {
397
+ IncomingFile& f = t->files[i];
398
+ f.final_path = t->is_directory ? combine_paths(dest_path, f.relative_path) : dest_path;
399
+ // Transfer ids are only unique per sender (every node's first offer is
400
+ // id 1), so the sender's PeerId is what keeps two peers' temp files
401
+ // apart. Use the full hex: short_hex() is 32 bits, which a peer could
402
+ // grind a keypair to collide with on purpose.
403
+ f.temp_path = combine_paths(config_.temp_directory,
404
+ from.to_hex() + "." + std::to_string(id) + "."
405
+ + std::to_string(i) + ".part");
406
+ }
407
+ t->status = Status::Active;
408
+ t->last_activity = std::chrono::steady_clock::now();
409
+ empty_transfer = t->files.empty();
410
+ }
411
+ create_directories(config_.temp_directory.c_str());
412
+ if (t->is_directory) create_directories(dest_path.c_str());
413
+
414
+ Bytes m; m.push_back(OP_RESPONSE); put_u64(m, id); m.push_back(1); send_to(from, m);
415
+ LOG_INFO("filexfer", "Accepted transfer [" << id << "] -> " << dest_path);
416
+
417
+ if (empty_transfer) { // e.g. an empty directory: done immediately
418
+ send_complete(from, id, true);
419
+ finish_incoming(t, true, "");
420
+ }
421
+ }
422
+
423
+ void FileTransfer::reject(const PeerId& from, uint64_t id) {
424
+ auto t = find_incoming(from, id);
425
+ {
426
+ std::lock_guard<std::mutex> lock(mutex_);
427
+ auto pit = incoming_.find(from);
428
+ if (pit != incoming_.end()) pit->second.erase(id);
429
+ }
430
+ Bytes m; m.push_back(OP_RESPONSE); put_u64(m, id); m.push_back(0); send_to(from, m);
431
+ if (t && complete_handler_) complete_handler_(id, false, "");
432
+ }
433
+
434
+ // ── Receive-side disk writer (off the reactor thread) ─────────────────────────
435
+ //
436
+ // The reactor validates a chunk and pushes it into t->wq; the writer pool drains
437
+ // that queue on its own thread. `t->out`, `t->hash`, `t->out_idx` and
438
+ // `t->hashing_file` are writer-thread-only: a transfer's `scheduled` flag keeps
439
+ // exactly one worker draining it at a time, so these need no lock even though the
440
+ // worker touches them outside t->mtx.
441
+
442
+ FileTransfer::Incoming::~Incoming() {
443
+ out.close(); // must precede the temp-file delete on Windows (can't unlink an open file)
444
+ for (auto& f : files) if (f.temp_created && !f.finalized) delete_file(f.temp_path.c_str());
445
+ }
446
+
447
+ void FileTransfer::schedule_writer(const std::shared_ptr<Incoming>& t) {
448
+ { std::lock_guard<std::mutex> lk(disk_mutex_); disk_ready_.push(t); }
449
+ disk_cv_.notify_one();
450
+ }
451
+
452
+ void FileTransfer::disk_worker_loop() {
453
+ while (running_.load()) {
454
+ std::shared_ptr<Incoming> t;
455
+ {
456
+ std::unique_lock<std::mutex> lk(disk_mutex_);
457
+ disk_cv_.wait(lk, [this] { return !running_.load() || !disk_ready_.empty(); });
458
+ if (!running_.load()) return;
459
+ t = std::move(disk_ready_.front());
460
+ disk_ready_.pop();
461
+ }
462
+ if (t) drain_writes(t);
463
+ }
464
+ }
465
+
466
+ void FileTransfer::drain_writes(const std::shared_ptr<Incoming>& t) {
467
+ for (;;) {
468
+ WriteJob job;
469
+ {
470
+ std::lock_guard<std::mutex> lk(t->mtx);
471
+ if (t->finished) { // torn down elsewhere: drop pending work
472
+ std::queue<WriteJob> empty; t->wq.swap(empty);
473
+ t->queued_bytes = 0; t->scheduled = false;
474
+ return;
475
+ }
476
+ if (t->wq.empty()) { t->scheduled = false; return; }
477
+ job = std::move(t->wq.front());
478
+ t->wq.pop();
479
+ }
480
+ if (job.is_file_end) process_file_end(t, job);
481
+ else process_data(t, job);
482
+ }
483
+ }
484
+
485
+ void FileTransfer::process_data(const std::shared_ptr<Incoming>& t, WriteJob& job) {
486
+ const uint32_t fidx = job.fidx;
487
+ std::string temp_path;
488
+ {
489
+ std::lock_guard<std::mutex> lk(t->mtx);
490
+ if (t->finished || fidx >= t->files.size()) return;
491
+ temp_path = t->files[fidx].temp_path;
492
+ }
493
+
494
+ // Open the temp file once per file, then write sequentially at the given offset.
495
+ if (t->out_idx != fidx) {
496
+ t->out.close();
497
+ if (!t->out.open_write(temp_path.c_str())) {
498
+ send_complete(t->peer, t->id, false);
499
+ finish_incoming(t, false, "cannot create temp file");
500
+ return;
501
+ }
502
+ t->out_idx = fidx;
503
+ { std::lock_guard<std::mutex> lk(t->mtx); t->files[fidx].temp_created = true; }
504
+ }
505
+ if (t->hashing_file != static_cast<int>(fidx)) { rats_sha256_reset(&t->hash); t->hashing_file = static_cast<int>(fidx); }
506
+
507
+ if (!t->out.write_at(job.offset, job.data.data(), job.data.size())) {
508
+ LOG_ERROR("filexfer", "transfer " << t->id << ": disk write failed");
509
+ send_complete(t->peer, t->id, false);
510
+ finish_incoming(t, false, "failed to write chunk to disk");
511
+ return;
512
+ }
513
+ rats_sha256_update(&t->hash, job.data.data(), job.data.size());
514
+
515
+ const uint64_t ack_interval =
516
+ std::min<uint64_t>(config_.progress_interval, std::max<uint32_t>(1, config_.window_bytes / 2));
517
+ bool send_ack = false; uint64_t ack_bytes = 0;
518
+ {
519
+ std::lock_guard<std::mutex> lk(t->mtx);
520
+ IncomingFile& f = t->files[fidx];
521
+ f.received += job.data.size();
522
+ t->bytes_done += job.data.size();
523
+ if (t->queued_bytes >= job.data.size()) t->queued_bytes -= job.data.size();
524
+ t->last_activity = std::chrono::steady_clock::now();
525
+ // Ack on the byte threshold, and always once a file's data is fully on disk
526
+ // (keeps the sender's window moving across file boundaries).
527
+ if (f.received >= f.size || t->bytes_done - t->last_ack >= ack_interval) {
528
+ t->last_ack = t->bytes_done; ack_bytes = t->bytes_done; send_ack = true;
529
+ }
530
+ }
531
+ { std::lock_guard<std::mutex> lk(stats_mutex_); stats_.bytes_received += job.data.size(); }
532
+
533
+ if (send_ack) { Bytes m; m.push_back(OP_PROGRESS); put_u64(m, t->id); put_u64(m, ack_bytes); send_to(t->peer, m); }
534
+ emit_progress(t);
535
+ }
536
+
537
+ void FileTransfer::process_file_end(const std::shared_ptr<Incoming>& t, WriteJob& job) {
538
+ const uint32_t fidx = job.fidx;
539
+ std::string final_path, temp_path, rel;
540
+ uint64_t fsize = 0;
541
+ {
542
+ std::lock_guard<std::mutex> lk(t->mtx);
543
+ if (t->finished || fidx >= t->files.size()) return;
544
+ IncomingFile& f = t->files[fidx];
545
+ if (f.finalized) return;
546
+ final_path = f.final_path; temp_path = f.temp_path; rel = f.relative_path; fsize = f.size;
547
+ }
548
+
549
+ // Whole-file SHA-256 over exactly the bytes written to disk (a file with no
550
+ // data — an empty file — hashes the empty input).
551
+ if (t->hashing_file != static_cast<int>(fidx)) { rats_sha256_reset(&t->hash); t->hashing_file = static_cast<int>(fidx); }
552
+ uint8_t local[RATS_SHA256_HASH_SIZE];
553
+ rats_sha256_finish(&t->hash, local);
554
+ t->hashing_file = -1;
555
+ if (config_.verify_integrity && std::memcmp(local, job.sha, RATS_SHA256_HASH_SIZE) != 0) {
556
+ LOG_ERROR("filexfer", "SHA-256 mismatch for " << rel << " on transfer " << t->id);
557
+ send_complete(t->peer, t->id, false);
558
+ finish_incoming(t, false, "SHA-256 mismatch");
559
+ return;
560
+ }
561
+
562
+ // Close the temp handle before moving it into place (Windows can't rename an
563
+ // open file).
564
+ if (t->out_idx == fidx) { t->out.close(); t->out_idx = SIZE_MAX; }
565
+
566
+ const std::string parent = get_parent_directory(final_path.c_str());
567
+ if (!parent.empty()) create_directories(parent.c_str());
568
+ if (file_exists(final_path.c_str())) delete_file(final_path.c_str());
569
+
570
+ bool ok;
571
+ if (fsize == 0) {
572
+ ok = create_file_with_size(final_path.c_str(), 0);
573
+ if (file_exists(temp_path.c_str())) delete_file(temp_path.c_str());
574
+ } else {
575
+ ok = rename_file(temp_path.c_str(), final_path.c_str());
576
+ if (!ok) { // rename fails across volumes; fall back to copy + delete
577
+ ok = copy_file(temp_path.c_str(), final_path.c_str());
578
+ if (ok) delete_file(temp_path.c_str());
579
+ }
580
+ }
581
+ if (!ok) {
582
+ LOG_ERROR("filexfer", "cannot place file " << final_path);
583
+ send_complete(t->peer, t->id, false);
584
+ finish_incoming(t, false, "cannot write destination file");
585
+ return;
586
+ }
587
+
588
+ bool all_done = false;
589
+ {
590
+ std::lock_guard<std::mutex> lk(t->mtx);
591
+ IncomingFile& f = t->files[fidx];
592
+ f.finalized = true;
593
+ t->files_done++;
594
+ all_done = true;
595
+ for (const auto& ff : t->files) if (!ff.finalized) { all_done = false; break; }
596
+ }
597
+ emit_progress(t);
598
+
599
+ if (all_done) {
600
+ send_complete(t->peer, t->id, true);
601
+ finish_incoming(t, true, "");
602
+ }
603
+ }
604
+
605
+ void FileTransfer::finish_incoming(const std::shared_ptr<Incoming>& t, bool success, const std::string& error) {
606
+ std::string dest;
607
+ bool cancelled;
608
+ {
609
+ std::lock_guard<std::mutex> lk(t->mtx);
610
+ if (t->finished) return;
611
+ t->finished = true;
612
+ cancelled = (t->status == Status::Cancelled);
613
+ if (!cancelled)
614
+ t->status = success ? Status::Completed : Status::Failed;
615
+ dest = t->dest_root;
616
+ // Partial temp files are reclaimed by ~Incoming, once the disk writer has
617
+ // released its handle — deleting here could race an in-flight write.
618
+ }
619
+ {
620
+ std::lock_guard<std::mutex> lock(mutex_);
621
+ auto pit = incoming_.find(t->peer);
622
+ if (pit != incoming_.end()) pit->second.erase(t->id);
623
+ }
624
+ { std::lock_guard<std::mutex> lk(stats_mutex_); success ? ++stats_.completed : ++stats_.failed; }
625
+ emit_progress(t);
626
+ if (cancelled) LOG_INFO("filexfer", "Receive [" << t->id << "] cancelled");
627
+ else if (success) LOG_INFO("filexfer", "Receive [" << t->id << "] completed -> " << dest);
628
+ else if (error.empty()) LOG_WARN("filexfer", "Receive [" << t->id << "] failed");
629
+ else LOG_WARN("filexfer", "Receive [" << t->id << "] failed: " << error);
630
+ if (complete_handler_) complete_handler_(t->id, success, dest);
631
+ }
632
+
633
+ // ── Message dispatch (reactor thread) ────────────────────────────────────────
634
+
635
+ void FileTransfer::on_message(const Peer& peer, ByteView payload) {
636
+ Reader r{payload.data(), payload.data() + payload.size()};
637
+ const uint8_t op = r.u8();
638
+ const PeerId from = peer.id();
639
+
640
+ switch (op) {
641
+ case OP_OFFER: {
642
+ const uint64_t id = r.u64();
643
+ const bool is_dir = r.u8() != 0;
644
+ const uint64_t total = r.u64();
645
+ const uint16_t nlen = r.u16();
646
+ ByteView nm = r.bytes(nlen);
647
+ const uint32_t count = r.u32();
648
+ if (!r.ok) return;
649
+ // `count` is the peer's word. Reserving on it directly lets a ~30-byte
650
+ // message ask for hundreds of GB; the resulting bad_alloc/length_error
651
+ // would unwind out of the reactor thread, which has no handler above it,
652
+ // and abort the process. Reject a count the rest of the payload cannot
653
+ // possibly hold, then cap the speculative reserve so memory stays
654
+ // proportional to the entries actually parsed.
655
+ if (static_cast<uint64_t>(count) * kMinManifestEntry > r.remaining()) {
656
+ LOG_WARN("filexfer", "Dropping offer " << id << " from " << from.short_hex()
657
+ << ": manifest claims " << count << " file(s) but only "
658
+ << r.remaining() << " B remain");
659
+ return;
660
+ }
661
+ std::string name(reinterpret_cast<const char*>(nm.data()), nm.size());
662
+ std::vector<FileEntry> files;
663
+ files.reserve(std::min<uint32_t>(count, 4096));
664
+ for (uint32_t i = 0; i < count; ++i) {
665
+ const uint16_t plen = r.u16();
666
+ ByteView pb = r.bytes(plen);
667
+ const uint64_t sz = r.u64();
668
+ if (!r.ok) return;
669
+ files.push_back({std::string(reinterpret_cast<const char*>(pb.data()), pb.size()), sz});
670
+ }
671
+ handle_offer(from, id, is_dir, total, std::move(name), std::move(files));
672
+ return;
673
+ }
674
+ case OP_RESPONSE: {
675
+ const uint64_t id = r.u64();
676
+ const uint8_t accepted = r.u8();
677
+ if (!r.ok) return;
678
+ auto t = find_outgoing(id);
679
+ if (!t) return;
680
+ if (!accepted) { finish_outgoing(t, false); return; }
681
+ { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Pending) t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); }
682
+ queue_send(id);
683
+ return;
684
+ }
685
+ case OP_CHUNK: {
686
+ const uint64_t id = r.u64();
687
+ const uint32_t fidx = r.u32();
688
+ const uint64_t offset = r.u64();
689
+ ByteView data = r.rest();
690
+ if (!r.ok) return;
691
+ handle_chunk(from, id, fidx, offset, data);
692
+ return;
693
+ }
694
+ case OP_FILE_END: {
695
+ const uint64_t id = r.u64();
696
+ const uint32_t fidx = r.u32();
697
+ ByteView sha = r.bytes(RATS_SHA256_HASH_SIZE);
698
+ if (!r.ok) return;
699
+ handle_file_end(from, id, fidx, sha.data());
700
+ return;
701
+ }
702
+ case OP_PROGRESS: {
703
+ const uint64_t id = r.u64();
704
+ const uint64_t received = r.u64();
705
+ if (!r.ok) return;
706
+ auto t = find_outgoing(id);
707
+ if (!t) return;
708
+ { std::lock_guard<std::mutex> lk(t->mtx); t->acked = received; t->last_activity = std::chrono::steady_clock::now(); }
709
+ t->cv.notify_all();
710
+ return;
711
+ }
712
+ case OP_COMPLETE: {
713
+ const uint64_t id = r.u64();
714
+ const uint8_t ok = r.u8();
715
+ if (!r.ok) return;
716
+ if (auto t = find_outgoing(id)) finish_outgoing(t, ok != 0);
717
+ return;
718
+ }
719
+ case OP_CANCEL: {
720
+ const uint64_t id = r.u64();
721
+ if (!r.ok) return;
722
+ if (auto t = find_outgoing(id)) {
723
+ { std::lock_guard<std::mutex> lk(t->mtx); t->status = Status::Cancelled; t->cv.notify_all(); }
724
+ finish_outgoing(t, false);
725
+ }
726
+ if (auto t = find_incoming(from, id)) {
727
+ { std::lock_guard<std::mutex> lk(t->mtx); t->status = Status::Cancelled; }
728
+ finish_incoming(t, false, "cancelled by peer");
729
+ }
730
+ return;
731
+ }
732
+ case OP_PAUSE: {
733
+ const uint64_t id = r.u64();
734
+ if (!r.ok) return;
735
+ if (auto t = find_outgoing(id)) { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) t->status = Status::Paused; t->cv.notify_all(); }
736
+ if (auto t = find_incoming(from, id)) { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) t->status = Status::Paused; }
737
+ return;
738
+ }
739
+ case OP_RESUME: {
740
+ const uint64_t id = r.u64();
741
+ if (!r.ok) return;
742
+ if (auto t = find_outgoing(id)) {
743
+ bool requeue = false;
744
+ { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); requeue = true; } t->cv.notify_all(); }
745
+ if (requeue) queue_send(id);
746
+ }
747
+ if (auto t = find_incoming(from, id)) { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); } }
748
+ return;
749
+ }
750
+ default: return;
751
+ }
752
+ }
753
+
754
+ void FileTransfer::handle_offer(const PeerId& from, uint64_t id, bool is_dir, uint64_t total,
755
+ std::string name, std::vector<FileEntry> files) {
756
+ if (find_incoming(from, id)) return; // duplicate offer
757
+
758
+ // Validate every peer-supplied relative path in a directory manifest before
759
+ // it can be used to build a destination path.
760
+ if (is_dir) {
761
+ for (const auto& f : files) {
762
+ if (!is_safe_relative_path(f.relative_path)) {
763
+ LOG_WARN("filexfer", "Rejecting offer " << id << " from " << from.short_hex()
764
+ << ": unsafe path in manifest ('" << f.relative_path << "')");
765
+ Bytes m; m.push_back(OP_RESPONSE); put_u64(m, id); m.push_back(0); send_to(from, m);
766
+ return;
767
+ }
768
+ }
769
+ }
770
+
771
+ auto t = std::make_shared<Incoming>();
772
+ t->id = id; t->peer = from; t->name = std::move(name); t->is_directory = is_dir;
773
+ t->last_activity = std::chrono::steady_clock::now();
774
+ t->files.reserve(files.size());
775
+ for (auto& f : files) {
776
+ IncomingFile inf; inf.relative_path = f.relative_path; inf.size = f.size;
777
+ t->files.push_back(std::move(inf));
778
+ }
779
+ Offer offer{from, id, t->name, total, is_dir, std::move(files)};
780
+ { std::lock_guard<std::mutex> lock(mutex_); incoming_[from].emplace(id, t); }
781
+
782
+ if (offer_handler_) offer_handler_(offer);
783
+ else reject(from, id); // no handler → auto-reject
784
+ }
785
+
786
+ // Reactor thread: validate ordering + copy the chunk into the transfer's write
787
+ // queue, then hand it to the disk-writer pool. No disk I/O or hashing happens
788
+ // here — those would block the reactor, which serves every peer on this shard.
789
+ void FileTransfer::handle_chunk(const PeerId& from, uint64_t id, uint32_t fidx, uint64_t offset,
790
+ ByteView data) {
791
+ auto t = find_incoming(from, id);
792
+ if (!t) return;
793
+
794
+ std::string fail;
795
+ bool need_schedule = false;
796
+ {
797
+ std::lock_guard<std::mutex> lk(t->mtx);
798
+ if (t->finished) return;
799
+ if (t->status != Status::Active && t->status != Status::Paused) return;
800
+ if (fidx >= t->files.size() || fidx != t->recv_file) {
801
+ fail = "out-of-order file index";
802
+ } else {
803
+ IncomingFile& f = t->files[fidx];
804
+ if (offset != f.enqueued) fail = "out-of-order chunk offset";
805
+ else if (offset + data.size() > f.size) fail = "chunk exceeds declared file size";
806
+ else {
807
+ WriteJob job;
808
+ job.fidx = fidx; job.offset = offset; job.data = data.to_bytes();
809
+ f.enqueued += data.size();
810
+ t->queued_bytes += data.size();
811
+ t->last_activity = std::chrono::steady_clock::now();
812
+ t->wq.push(std::move(job));
813
+ if (!t->scheduled) { t->scheduled = true; need_schedule = true; }
814
+ }
815
+ }
816
+ }
817
+ if (!fail.empty()) {
818
+ LOG_ERROR("filexfer", "transfer " << id << ": " << fail);
819
+ send_complete(from, id, false);
820
+ finish_incoming(t, false, fail);
821
+ return;
822
+ }
823
+ if (need_schedule) schedule_writer(t);
824
+ }
825
+
826
+ void FileTransfer::handle_file_end(const PeerId& from, uint64_t id, uint32_t fidx, const uint8_t* sha) {
827
+ auto t = find_incoming(from, id);
828
+ if (!t) return;
829
+ std::string fail;
830
+ bool need_schedule = false;
831
+ {
832
+ std::lock_guard<std::mutex> lk(t->mtx);
833
+ if (t->finished || fidx >= t->files.size()) return;
834
+ if (fidx != t->recv_file) {
835
+ fail = "file-end out of order";
836
+ } else {
837
+ IncomingFile& f = t->files[fidx];
838
+ if (f.enqueued != f.size) { // all of the file's data must be queued first
839
+ fail = "file-end before all data";
840
+ } else {
841
+ WriteJob job;
842
+ job.fidx = fidx; job.is_file_end = true;
843
+ std::memcpy(job.sha, sha, RATS_SHA256_HASH_SIZE);
844
+ t->wq.push(std::move(job));
845
+ t->recv_file++; // subsequent chunks belong to the next file
846
+ if (!t->scheduled) { t->scheduled = true; need_schedule = true; }
847
+ }
848
+ }
849
+ }
850
+ if (!fail.empty()) {
851
+ LOG_ERROR("filexfer", "transfer " << id << ": " << fail);
852
+ send_complete(from, id, false);
853
+ finish_incoming(t, false, fail);
854
+ return;
855
+ }
856
+ if (need_schedule) schedule_writer(t);
857
+ }
858
+
859
+ // ── Control API (works from either side) ──────────────────────────────────────
860
+
861
+ bool FileTransfer::cancel(const PeerId& peer, uint64_t id) {
862
+ bool acted = false;
863
+ if (auto t = find_outgoing(id)) {
864
+ { std::lock_guard<std::mutex> lk(t->mtx); if (!t->finished) { t->status = Status::Cancelled; t->cv.notify_all(); acted = true; } }
865
+ if (acted) { send_simple(t->peer, OP_CANCEL, id); finish_outgoing(t, false); }
866
+ }
867
+ if (auto t = find_incoming(peer, id)) {
868
+ bool a = false;
869
+ { std::lock_guard<std::mutex> lk(t->mtx); if (!t->finished) { t->status = Status::Cancelled; a = true; } }
870
+ if (a) { send_simple(peer, OP_CANCEL, id); finish_incoming(t, false, "cancelled"); acted = true; }
871
+ }
872
+ return acted;
873
+ }
874
+
875
+ bool FileTransfer::pause(const PeerId& peer, uint64_t id) {
876
+ if (auto t = find_outgoing(id)) {
877
+ bool ok = false;
878
+ { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) { t->status = Status::Paused; ok = true; t->cv.notify_all(); } }
879
+ if (ok) { send_simple(t->peer, OP_PAUSE, id); return true; }
880
+ }
881
+ if (auto t = find_incoming(peer, id)) {
882
+ bool ok = false;
883
+ { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) { t->status = Status::Paused; ok = true; } }
884
+ if (ok) { send_simple(peer, OP_PAUSE, id); return true; }
885
+ }
886
+ return false;
887
+ }
888
+
889
+ bool FileTransfer::resume(const PeerId& peer, uint64_t id) {
890
+ if (auto t = find_outgoing(id)) {
891
+ bool ok = false;
892
+ { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); ok = true; t->cv.notify_all(); } }
893
+ if (ok) { send_simple(t->peer, OP_RESUME, id); queue_send(id); return true; }
894
+ }
895
+ if (auto t = find_incoming(peer, id)) {
896
+ bool ok = false;
897
+ { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); ok = true; } }
898
+ if (ok) { send_simple(peer, OP_RESUME, id); return true; }
899
+ }
900
+ return false;
901
+ }
902
+
903
+ // ── Progress callbacks ────────────────────────────────────────────────────────
904
+
905
+ void FileTransfer::emit_progress(const std::shared_ptr<Outgoing>& t) {
906
+ if (!progress_handler_) return;
907
+ const auto now = std::chrono::steady_clock::now();
908
+ Progress p;
909
+ { std::lock_guard<std::mutex> lk(t->mtx);
910
+ t->rate.sample(t->bytes_done, now);
911
+ p.id = t->id; p.peer = t->peer; p.direction = Direction::Sending; p.status = t->status;
912
+ p.bytes_transferred = t->bytes_done; p.total_bytes = t->total_bytes;
913
+ p.files_completed = t->files_done; p.total_files = static_cast<uint32_t>(t->files.size());
914
+ t->rate.fill(p, t->bytes_done, t->total_bytes, now); }
915
+ progress_handler_(p);
916
+ }
917
+
918
+ void FileTransfer::emit_progress(const std::shared_ptr<Incoming>& t) {
919
+ if (!progress_handler_) return;
920
+ const auto now = std::chrono::steady_clock::now();
921
+ Progress p;
922
+ { std::lock_guard<std::mutex> lk(t->mtx);
923
+ uint64_t total = 0; for (auto& f : t->files) total += f.size;
924
+ t->rate.sample(t->bytes_done, now);
925
+ p.id = t->id; p.peer = t->peer; p.direction = Direction::Receiving; p.status = t->status;
926
+ p.bytes_transferred = t->bytes_done; p.total_bytes = total;
927
+ p.files_completed = t->files_done; p.total_files = static_cast<uint32_t>(t->files.size());
928
+ t->rate.fill(p, t->bytes_done, total, now); }
929
+ progress_handler_(p);
930
+ }
931
+
932
+ // ── Maintenance: idle timeout + purge ────────────────────────────────────────
933
+
934
+ void FileTransfer::maintenance_loop() {
935
+ while (running_.load()) {
936
+ {
937
+ std::unique_lock<std::mutex> lk(maintenance_mutex_);
938
+ maintenance_cv_.wait_for(lk, std::chrono::seconds(2), [this] { return !running_.load(); });
939
+ }
940
+ if (!running_.load()) return;
941
+
942
+ const auto now = std::chrono::steady_clock::now();
943
+ const uint32_t timeout = config_.transfer_timeout_secs;
944
+
945
+ std::vector<std::shared_ptr<Outgoing>> out;
946
+ std::vector<std::shared_ptr<Incoming>> in;
947
+ {
948
+ std::lock_guard<std::mutex> lock(mutex_);
949
+ for (auto& [id, t] : outgoing_) out.push_back(t);
950
+ for (auto& [peer, m] : incoming_) for (auto& [id, t] : m) in.push_back(t);
951
+ }
952
+
953
+ for (auto& t : out) {
954
+ bool stale = false;
955
+ { std::lock_guard<std::mutex> lk(t->mtx);
956
+ if (!t->finished && t->status != Status::Paused &&
957
+ std::chrono::duration_cast<std::chrono::seconds>(now - t->last_activity).count() > timeout)
958
+ stale = true; }
959
+ if (stale) { LOG_WARN("filexfer", "outgoing transfer " << t->id << " timed out"); send_complete(t->peer, t->id, false); finish_outgoing(t, false); }
960
+ }
961
+ for (auto& t : in) {
962
+ bool stale = false;
963
+ { std::lock_guard<std::mutex> lk(t->mtx);
964
+ if (!t->finished && t->status != Status::Paused && t->status != Status::Pending &&
965
+ std::chrono::duration_cast<std::chrono::seconds>(now - t->last_activity).count() > timeout)
966
+ stale = true; }
967
+ if (stale) { LOG_WARN("filexfer", "incoming transfer " << t->id << " timed out"); send_complete(t->peer, t->id, false); finish_incoming(t, false, "timed out"); }
968
+ }
969
+ }
970
+ }
971
+
972
+ } // namespace librats