librats 1.0.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/README.md +145 -331
  2. package/binding.gyp +16 -3
  3. package/lib/index.d.ts +288 -696
  4. package/lib/index.js +407 -44
  5. package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
  7. package/native-src/CMakeLists.txt +404 -179
  8. package/native-src/LICENSE +1 -1
  9. package/native-src/src/librats/bindings/rats.cpp +762 -0
  10. package/native-src/src/librats/bindings/rats.h +380 -0
  11. package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
  12. package/native-src/src/librats/bittorrent/bencode.h +176 -0
  13. package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
  14. package/native-src/src/librats/bittorrent/bitfield.h +76 -0
  15. package/native-src/src/librats/bittorrent/byte_io.h +58 -0
  16. package/native-src/src/librats/bittorrent/choker.cpp +25 -0
  17. package/native-src/src/librats/bittorrent/choker.h +46 -0
  18. package/native-src/src/librats/bittorrent/client.cpp +413 -0
  19. package/native-src/src/librats/bittorrent/client.h +227 -0
  20. package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
  21. package/native-src/src/librats/bittorrent/disk_io.h +150 -0
  22. package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
  23. package/native-src/src/librats/bittorrent/extensions.h +94 -0
  24. package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
  25. package/native-src/src/librats/bittorrent/file_storage.h +79 -0
  26. package/native-src/src/librats/bittorrent/log.h +42 -0
  27. package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
  28. package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
  29. package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
  30. package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
  31. package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
  32. package/native-src/src/librats/bittorrent/peer_list.h +75 -0
  33. package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
  34. package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
  35. package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
  36. package/native-src/src/librats/bittorrent/reactor.h +89 -0
  37. package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
  38. package/native-src/src/librats/bittorrent/resume_data.h +41 -0
  39. package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
  40. package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
  41. package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
  42. package/native-src/src/librats/bittorrent/torrent.h +260 -0
  43. package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
  44. package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
  45. package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
  46. package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
  47. package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
  48. package/native-src/src/librats/bittorrent/tracker.h +108 -0
  49. package/native-src/src/librats/bittorrent/types.cpp +206 -0
  50. package/native-src/src/librats/bittorrent/types.h +86 -0
  51. package/native-src/src/librats/core/address.cpp +35 -0
  52. package/native-src/src/librats/core/address.h +78 -0
  53. package/native-src/src/librats/core/bytes.h +69 -0
  54. package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
  55. package/native-src/src/librats/core/chained_send_buffer.h +183 -0
  56. package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
  57. package/native-src/src/librats/core/endpoint_parse.h +31 -0
  58. package/native-src/src/librats/core/event_bus.h +70 -0
  59. package/native-src/src/librats/core/host_endpoint.h +56 -0
  60. package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
  61. package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
  62. package/native-src/src/librats/core/ip_address.cpp +120 -0
  63. package/native-src/src/librats/core/ip_address.h +109 -0
  64. package/native-src/src/librats/core/mpsc_queue.h +47 -0
  65. package/native-src/src/librats/core/notifier.h +74 -0
  66. package/native-src/src/librats/core/receive_buffer.cpp +219 -0
  67. package/native-src/src/librats/core/receive_buffer.h +171 -0
  68. package/native-src/src/librats/core/service_registry.h +58 -0
  69. package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
  70. package/native-src/src/librats/core/socket.h +496 -0
  71. package/native-src/src/librats/core/timer_queue.h +105 -0
  72. package/native-src/src/librats/core/types.cpp +43 -0
  73. package/native-src/src/librats/core/types.h +103 -0
  74. package/native-src/src/librats/core/wakeup_pipe.h +83 -0
  75. package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
  76. package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
  77. package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
  78. package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
  79. package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
  80. package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
  81. package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
  82. package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
  83. package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
  84. package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
  85. package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
  86. package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
  87. package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
  88. package/native-src/src/librats/crypto/hkdf.c +266 -0
  89. package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
  90. package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
  91. package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
  92. package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
  93. package/native-src/src/librats/crypto/poly1305.h +37 -0
  94. package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
  95. package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
  96. package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
  97. package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
  98. package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
  99. package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
  100. package/native-src/src/librats/dht/announce.cpp +37 -0
  101. package/native-src/src/librats/dht/announce.h +41 -0
  102. package/native-src/src/librats/dht/bep42.cpp +109 -0
  103. package/native-src/src/librats/dht/bep42.h +48 -0
  104. package/native-src/src/librats/dht/dht.cpp +501 -0
  105. package/native-src/src/librats/dht/dht.h +119 -0
  106. package/native-src/src/librats/dht/dht_runner.cpp +103 -0
  107. package/native-src/src/librats/dht/dht_runner.h +71 -0
  108. package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
  109. package/native-src/src/librats/dht/dos_blocker.h +47 -0
  110. package/native-src/src/librats/dht/find_peers.cpp +52 -0
  111. package/native-src/src/librats/dht/find_peers.h +73 -0
  112. package/native-src/src/librats/dht/id.h +167 -0
  113. package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
  114. package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
  115. package/native-src/src/librats/dht/log.h +38 -0
  116. package/native-src/src/librats/dht/node.cpp +473 -0
  117. package/native-src/src/librats/dht/node.h +164 -0
  118. package/native-src/src/librats/dht/node_entry.h +81 -0
  119. package/native-src/src/librats/dht/observer.h +72 -0
  120. package/native-src/src/librats/dht/persistence.cpp +90 -0
  121. package/native-src/src/librats/dht/persistence.h +32 -0
  122. package/native-src/src/librats/dht/routing_table.cpp +559 -0
  123. package/native-src/src/librats/dht/routing_table.h +185 -0
  124. package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
  125. package/native-src/src/librats/dht/rpc_manager.h +77 -0
  126. package/native-src/src/librats/dht/storage.cpp +92 -0
  127. package/native-src/src/librats/dht/storage.h +74 -0
  128. package/native-src/src/librats/dht/transport.h +27 -0
  129. package/native-src/src/librats/dht/traversal.cpp +326 -0
  130. package/native-src/src/librats/dht/traversal.h +120 -0
  131. package/native-src/src/librats/dht/udp_transport.cpp +49 -0
  132. package/native-src/src/librats/dht/udp_transport.h +51 -0
  133. package/native-src/src/librats/mdns/log.h +22 -0
  134. package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
  135. package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
  136. package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
  137. package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
  138. package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
  139. package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
  140. package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
  141. package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
  142. package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
  143. package/native-src/src/librats/node/circuit_service.h +84 -0
  144. package/native-src/src/librats/node/config.h +110 -0
  145. package/native-src/src/librats/node/dial_service.h +54 -0
  146. package/native-src/src/librats/node/dialer.cpp +264 -0
  147. package/native-src/src/librats/node/dialer.h +188 -0
  148. package/native-src/src/librats/node/host_events.h +26 -0
  149. package/native-src/src/librats/node/identify.cpp +130 -0
  150. package/native-src/src/librats/node/identify.h +71 -0
  151. package/native-src/src/librats/node/nat_status.cpp +103 -0
  152. package/native-src/src/librats/node/nat_status.h +118 -0
  153. package/native-src/src/librats/node/node.cpp +865 -0
  154. package/native-src/src/librats/node/node.h +344 -0
  155. package/native-src/src/librats/node/node_context.h +33 -0
  156. package/native-src/src/librats/node/peer_network.h +91 -0
  157. package/native-src/src/librats/peer/peer.h +49 -0
  158. package/native-src/src/librats/peer/peer_book.cpp +181 -0
  159. package/native-src/src/librats/peer/peer_book.h +88 -0
  160. package/native-src/src/librats/peer/peer_id.cpp +72 -0
  161. package/native-src/src/librats/peer/peer_id.h +62 -0
  162. package/native-src/src/librats/peer/peer_info.h +37 -0
  163. package/native-src/src/librats/peer/peer_table.cpp +170 -0
  164. package/native-src/src/librats/peer/peer_table.h +148 -0
  165. package/native-src/src/librats/security/handshaker.h +66 -0
  166. package/native-src/src/librats/security/identity.h +43 -0
  167. package/native-src/src/librats/security/noise_security.cpp +122 -0
  168. package/native-src/src/librats/security/noise_security.h +37 -0
  169. package/native-src/src/librats/security/plaintext_security.h +106 -0
  170. package/native-src/src/librats/security/session.h +37 -0
  171. package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
  172. package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
  173. package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
  174. package/native-src/src/librats/subsystems/bittorrent.h +136 -0
  175. package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
  176. package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
  177. package/native-src/src/librats/subsystems/dht_service.h +36 -0
  178. package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
  179. package/native-src/src/librats/subsystems/file_transfer.h +367 -0
  180. package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
  181. package/native-src/src/librats/subsystems/hole_punch.h +290 -0
  182. package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
  183. package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
  184. package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
  185. package/native-src/src/librats/subsystems/message_json.cpp +112 -0
  186. package/native-src/src/librats/subsystems/message_json.h +88 -0
  187. package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
  188. package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
  189. package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
  190. package/native-src/src/librats/subsystems/ping_service.h +66 -0
  191. package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
  192. package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
  193. package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
  194. package/native-src/src/librats/subsystems/pubsub.h +175 -0
  195. package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
  196. package/native-src/src/librats/subsystems/reconnection.h +126 -0
  197. package/native-src/src/librats/subsystems/relay.cpp +1142 -0
  198. package/native-src/src/librats/subsystems/relay.h +211 -0
  199. package/native-src/src/librats/subsystems/relay_service.h +46 -0
  200. package/native-src/src/librats/transport/connection.cpp +343 -0
  201. package/native-src/src/librats/transport/connection.h +283 -0
  202. package/native-src/src/librats/transport/link.h +96 -0
  203. package/native-src/src/librats/transport/reactor.cpp +588 -0
  204. package/native-src/src/librats/transport/reactor.h +262 -0
  205. package/native-src/src/librats/transport/reactor_pool.h +81 -0
  206. package/native-src/src/librats/transport/relay_link.cpp +208 -0
  207. package/native-src/src/librats/transport/relay_link.h +303 -0
  208. package/native-src/src/librats/transport/tcp_link.cpp +49 -0
  209. package/native-src/src/librats/transport/tcp_link.h +43 -0
  210. package/native-src/src/librats/transport/udp_mux.cpp +617 -0
  211. package/native-src/src/librats/transport/udp_mux.h +363 -0
  212. package/native-src/src/librats/transport/udp_packet.cpp +121 -0
  213. package/native-src/src/librats/transport/udp_packet.h +190 -0
  214. package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
  215. package/native-src/src/librats/transport/udp_stream.h +614 -0
  216. package/native-src/src/librats/util/features.h.in +51 -0
  217. package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
  218. package/native-src/src/librats/util/fs.h +136 -0
  219. package/native-src/src/librats/util/json.cpp +1002 -0
  220. package/native-src/src/librats/util/json.h +444 -0
  221. package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
  222. package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
  223. package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
  224. package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
  225. package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
  226. package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
  227. package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
  228. package/native-src/src/librats/util/rats_export.h +69 -0
  229. package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
  230. package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
  231. package/native-src/src/librats/wire/frame.cpp +76 -0
  232. package/native-src/src/librats/wire/frame.h +111 -0
  233. package/native-src/src/librats/wire/message_router.cpp +45 -0
  234. package/native-src/src/librats/wire/message_router.h +47 -0
  235. package/package.json +5 -4
  236. package/scripts/build-librats.js +1 -0
  237. package/scripts/postinstall.js +3 -3
  238. package/scripts/prepare-package.js +4 -4
  239. package/scripts/verify-installation.js +63 -105
  240. package/src/librats_node.cpp +1067 -1323
  241. package/native-src/src/bencode.cpp +0 -485
  242. package/native-src/src/bencode.h +0 -145
  243. package/native-src/src/bittorrent.cpp +0 -14
  244. package/native-src/src/bittorrent.h +0 -74
  245. package/native-src/src/bt_bitfield.cpp +0 -372
  246. package/native-src/src/bt_bitfield.h +0 -316
  247. package/native-src/src/bt_choker.cpp +0 -228
  248. package/native-src/src/bt_choker.h +0 -147
  249. package/native-src/src/bt_client.cpp +0 -1047
  250. package/native-src/src/bt_client.h +0 -445
  251. package/native-src/src/bt_create_torrent.cpp +0 -677
  252. package/native-src/src/bt_create_torrent.h +0 -473
  253. package/native-src/src/bt_extension.cpp +0 -469
  254. package/native-src/src/bt_extension.h +0 -309
  255. package/native-src/src/bt_file_storage.cpp +0 -261
  256. package/native-src/src/bt_file_storage.h +0 -298
  257. package/native-src/src/bt_handshake.cpp +0 -134
  258. package/native-src/src/bt_handshake.h +0 -157
  259. package/native-src/src/bt_messages.cpp +0 -364
  260. package/native-src/src/bt_messages.h +0 -324
  261. package/native-src/src/bt_network.cpp +0 -1007
  262. package/native-src/src/bt_network.h +0 -417
  263. package/native-src/src/bt_peer_connection.cpp +0 -742
  264. package/native-src/src/bt_peer_connection.h +0 -592
  265. package/native-src/src/bt_piece_picker.cpp +0 -786
  266. package/native-src/src/bt_piece_picker.h +0 -473
  267. package/native-src/src/bt_resume_data.cpp +0 -410
  268. package/native-src/src/bt_resume_data.h +0 -249
  269. package/native-src/src/bt_torrent.cpp +0 -2120
  270. package/native-src/src/bt_torrent.h +0 -641
  271. package/native-src/src/bt_torrent_info.cpp +0 -659
  272. package/native-src/src/bt_torrent_info.h +0 -418
  273. package/native-src/src/bt_types.h +0 -621
  274. package/native-src/src/chained_send_buffer.cpp +0 -75
  275. package/native-src/src/chained_send_buffer.h +0 -137
  276. package/native-src/src/crypto/hkdf.c +0 -266
  277. package/native-src/src/crypto/poly1305.h +0 -36
  278. package/native-src/src/dht.cpp +0 -3311
  279. package/native-src/src/dht.h +0 -717
  280. package/native-src/src/disk_io.cpp +0 -632
  281. package/native-src/src/disk_io.h +0 -315
  282. package/native-src/src/file_transfer.cpp +0 -1415
  283. package/native-src/src/file_transfer.h +0 -286
  284. package/native-src/src/fs.h +0 -108
  285. package/native-src/src/gossipsub.cpp +0 -1139
  286. package/native-src/src/gossipsub.h +0 -403
  287. package/native-src/src/ice.cpp +0 -893
  288. package/native-src/src/ice.h +0 -559
  289. package/native-src/src/json.hpp +0 -25526
  290. package/native-src/src/librats.cpp +0 -2378
  291. package/native-src/src/librats.h +0 -2324
  292. package/native-src/src/librats_bittorrent.cpp +0 -601
  293. package/native-src/src/librats_c.cpp +0 -1557
  294. package/native-src/src/librats_c.h +0 -323
  295. package/native-src/src/librats_discovery.cpp +0 -402
  296. package/native-src/src/librats_encryption.cpp +0 -275
  297. package/native-src/src/librats_file_transfer.cpp +0 -144
  298. package/native-src/src/librats_gossipsub.cpp +0 -289
  299. package/native-src/src/librats_ice.cpp +0 -213
  300. package/native-src/src/librats_log_macros.h +0 -36
  301. package/native-src/src/librats_logging.cpp +0 -173
  302. package/native-src/src/librats_mdns.cpp +0 -166
  303. package/native-src/src/librats_persistence.cpp +0 -796
  304. package/native-src/src/librats_portmap.cpp +0 -419
  305. package/native-src/src/librats_reconnection.cpp +0 -218
  306. package/native-src/src/librats_statistic.cpp +0 -105
  307. package/native-src/src/librats_storage.cpp +0 -189
  308. package/native-src/src/rats_export.h +0 -17
  309. package/native-src/src/receive_buffer.cpp +0 -82
  310. package/native-src/src/receive_buffer.h +0 -127
  311. package/native-src/src/socket.h +0 -228
  312. package/native-src/src/threadmanager.cpp +0 -105
  313. package/native-src/src/threadmanager.h +0 -53
  314. package/native-src/src/tracker.cpp +0 -1264
  315. package/native-src/src/tracker.h +0 -319
  316. package/native-src/src/turn.cpp +0 -762
  317. package/native-src/src/turn.h +0 -460
  318. package/native-src/src/wakeup_pipe.h +0 -60
  319. /package/native-src/src/{os.h → librats/util/os.h} +0 -0
@@ -0,0 +1,209 @@
1
+ #include "librats/bittorrent/disk_io.h"
2
+
3
+ #include "librats/bittorrent/log.h"
4
+ #include "librats/crypto/sha1.h"
5
+ #include "librats/util/fs.h"
6
+
7
+ #include <algorithm>
8
+ #include <cstring>
9
+ #include <utility>
10
+
11
+ namespace librats::bittorrent {
12
+
13
+ ThreadedDiskIo::ThreadedDiskIo(const TorrentInfo& info, std::string save_path,
14
+ CompletionPoster poster, Config config)
15
+ : files_(info.files())
16
+ , piece_hashes_(info.piece_hashes())
17
+ , save_path_(std::move(save_path))
18
+ , poster_(std::move(poster))
19
+ , ensured_(info.num_files(), 0) {
20
+ const int n = (std::max)(1, config.num_threads);
21
+ workers_.reserve(std::size_t(n));
22
+ for (int i = 0; i < n; ++i) workers_.emplace_back([this] { worker_loop(); });
23
+ }
24
+
25
+ ThreadedDiskIo::~ThreadedDiskIo() {
26
+ stop();
27
+ }
28
+
29
+ void ThreadedDiskIo::stop() {
30
+ {
31
+ std::lock_guard<std::mutex> lock(mutex_);
32
+ if (stopping_) return;
33
+ stopping_ = true;
34
+ }
35
+ cv_.notify_all();
36
+ for (auto& t : workers_)
37
+ if (t.joinable()) t.join();
38
+ workers_.clear();
39
+ }
40
+
41
+ void ThreadedDiskIo::enqueue(std::function<void()> job) {
42
+ {
43
+ std::lock_guard<std::mutex> lock(mutex_);
44
+ if (stopping_) return;
45
+ jobs_.push(std::move(job));
46
+ }
47
+ cv_.notify_one();
48
+ }
49
+
50
+ void ThreadedDiskIo::worker_loop() {
51
+ for (;;) {
52
+ std::function<void()> job;
53
+ {
54
+ std::unique_lock<std::mutex> lock(mutex_);
55
+ cv_.wait(lock, [this] { return stopping_ || !jobs_.empty(); });
56
+ if (stopping_ && jobs_.empty()) return;
57
+ job = std::move(jobs_.front());
58
+ jobs_.pop();
59
+ }
60
+ job();
61
+ }
62
+ }
63
+
64
+ void ThreadedDiskIo::complete(std::function<void()> handler) {
65
+ if (poster_) poster_(std::move(handler));
66
+ else handler();
67
+ }
68
+
69
+ std::string ThreadedDiskIo::file_path(std::size_t file_index) const {
70
+ return combine_paths(save_path_, files_.file_at(file_index).path);
71
+ }
72
+
73
+ bool ThreadedDiskIo::ensure_file(std::size_t file_index) {
74
+ std::lock_guard<std::mutex> lock(ensure_mutex_);
75
+ if (ensured_[file_index]) return true;
76
+
77
+ const std::string path = file_path(file_index);
78
+ const std::int64_t size = files_.file_at(file_index).size;
79
+
80
+ const std::string parent = get_parent_directory(path.c_str());
81
+ if (!parent.empty() && !directory_exists(parent.c_str())) {
82
+ if (!create_directories(parent.c_str())) {
83
+ LOG_ERROR("bt.disk", "✗ failed to create directory " << parent);
84
+ return false;
85
+ }
86
+ }
87
+
88
+ if (file_exists(path.c_str())) {
89
+ // Extend a short (e.g. partially-downloaded) file without truncating it.
90
+ if (size > 0 && get_file_size(path.c_str()) < size) {
91
+ const std::uint8_t zero = 0;
92
+ if (!write_file_chunk(path, std::uint64_t(size - 1), &zero, 1)) {
93
+ LOG_ERROR("bt.disk", "✗ failed to extend file " << path << " to " << size << " bytes");
94
+ return false;
95
+ }
96
+ }
97
+ } else if (!create_file_with_size(path, std::uint64_t(size))) {
98
+ LOG_ERROR("bt.disk", "✗ failed to create file " << path << " (" << size << " bytes)");
99
+ return false;
100
+ }
101
+
102
+ ensured_[file_index] = 1;
103
+ return true;
104
+ }
105
+
106
+ bool ThreadedDiskIo::write_range(std::uint32_t piece, std::uint32_t offset, const Bytes& data) {
107
+ auto slices = files_.map_block(piece, offset, std::int64_t(data.size()));
108
+ std::int64_t in_pos = 0;
109
+ for (const auto& s : slices) {
110
+ if (!ensure_file(s.file_index)) return false;
111
+ if (!write_file_chunk(file_path(s.file_index), std::uint64_t(s.offset),
112
+ data.data() + in_pos, std::size_t(s.size))) {
113
+ return false;
114
+ }
115
+ in_pos += s.size;
116
+ }
117
+ return in_pos == std::int64_t(data.size());
118
+ }
119
+
120
+ bool ThreadedDiskIo::read_range(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
121
+ Bytes& out, bool use_store, bool allow_short) {
122
+ out.assign(length, 0);
123
+ auto slices = files_.map_block(piece, offset, std::int64_t(length));
124
+ std::int64_t out_pos = 0;
125
+ for (const auto& s : slices) {
126
+ const std::string path = file_path(s.file_index);
127
+ // On the check path a missing/short file is expected (piece not yet on
128
+ // disk); probe the size first so we don't trip read_file_chunk's error log.
129
+ if (allow_short && get_file_size(path.c_str()) < s.offset + s.size) return false;
130
+ if (!read_file_chunk(path, std::uint64_t(s.offset), out.data() + out_pos, std::size_t(s.size)))
131
+ return false;
132
+ out_pos += s.size;
133
+ }
134
+ if (use_store) store_.overlay(piece, offset, out);
135
+ return true;
136
+ }
137
+
138
+ void ThreadedDiskIo::async_read(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
139
+ DiskReadHandler handler) {
140
+ enqueue([this, piece, offset, length, handler = std::move(handler)]() mutable {
141
+ Bytes out;
142
+ const bool ok = read_range(piece, offset, length, out, /*use_store=*/true, /*allow_short=*/false);
143
+ complete([handler = std::move(handler), ok, out = std::move(out)]() mutable {
144
+ if (handler) handler(ok, std::move(out));
145
+ });
146
+ });
147
+ }
148
+
149
+ void ThreadedDiskIo::async_write(std::uint32_t piece, std::uint32_t offset, Bytes data,
150
+ DiskWriteHandler handler) {
151
+ // Park the block so reads/hashes see it before the write reaches disk, and
152
+ // count it toward the pending-write total that drives backpressure (D-2).
153
+ const std::size_t sz = data.size();
154
+ store_.insert(piece, offset, data);
155
+ queued_bytes_.fetch_add(sz, std::memory_order_relaxed);
156
+ enqueue([this, piece, offset, sz, data = std::move(data), handler = std::move(handler)]() mutable {
157
+ const bool ok = write_range(piece, offset, data);
158
+ store_.erase(piece, offset);
159
+ queued_bytes_.fetch_sub(sz, std::memory_order_relaxed);
160
+ complete([handler = std::move(handler), ok]() mutable {
161
+ if (handler) handler(ok);
162
+ });
163
+ });
164
+ }
165
+
166
+ void ThreadedDiskIo::async_hash(std::uint32_t piece, DiskHashHandler handler) {
167
+ enqueue([this, piece, handler = std::move(handler)]() mutable {
168
+ const std::uint32_t ps = files_.piece_size(piece);
169
+ Bytes buf;
170
+ const bool ok = read_range(piece, 0, ps, buf, /*use_store=*/true, /*allow_short=*/false);
171
+ std::array<std::uint8_t, 20> digest{};
172
+ if (ok) digest = SHA1::hash_raw(buf.data(), buf.size());
173
+ complete([handler = std::move(handler), ok, digest]() mutable {
174
+ if (handler) handler(ok, digest);
175
+ });
176
+ });
177
+ }
178
+
179
+ void ThreadedDiskIo::async_check_files(Bitfield trusted_have, DiskCheckProgress progress,
180
+ DiskCheckHandler handler) {
181
+ enqueue([this, trusted_have = std::move(trusted_have), progress = std::move(progress),
182
+ handler = std::move(handler)]() mutable {
183
+ const std::uint32_t n = files_.num_pieces();
184
+ const bool have_trust = trusted_have.size() == n;
185
+ Bitfield have(n, false);
186
+
187
+ for (std::uint32_t p = 0; p < n; ++p) {
188
+ if (have_trust && trusted_have.get(p)) {
189
+ have.set(p); // fast resume: trust the saved bit, skip hashing
190
+ } else {
191
+ Bytes buf;
192
+ if (read_range(p, 0, files_.piece_size(p), buf, /*use_store=*/false, /*allow_short=*/true)) {
193
+ auto digest = SHA1::hash_raw(buf.data(), buf.size());
194
+ if (std::memcmp(digest.data(), piece_hashes_.data() + std::size_t(p) * 20, 20) == 0)
195
+ have.set(p);
196
+ }
197
+ }
198
+ if (progress) {
199
+ const std::uint32_t done = p + 1;
200
+ complete([progress, done, n] { progress(done, n); });
201
+ }
202
+ }
203
+ complete([handler = std::move(handler), have = std::move(have)]() mutable {
204
+ if (handler) handler(std::move(have));
205
+ });
206
+ });
207
+ }
208
+
209
+ } // namespace librats::bittorrent
@@ -0,0 +1,150 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file disk_io.h
5
+ * @brief Asynchronous piece storage — the boundary between the network thread
6
+ * and the filesystem.
7
+ *
8
+ * The engine never blocks on disk: it submits async_read / async_write /
9
+ * async_hash / async_check_files jobs and is called back with the result. DiskIo
10
+ * is an interface so a torrent can be tested against an in-memory fake, and so
11
+ * the threading strategy is swappable.
12
+ *
13
+ * Completions are delivered through a CompletionPoster — a closure the owner
14
+ * supplies to marshal handlers onto its own thread (the reactor, in the full
15
+ * engine). With no poster, handlers run inline on the disk-worker thread.
16
+ *
17
+ * Offsets passed to read/write/hash are relative to the *piece* (piece index +
18
+ * byte offset within it); ThreadedDiskIo translates them to file regions via
19
+ * FileStorage::map_block.
20
+ */
21
+
22
+ #include "librats/bittorrent/bitfield.h"
23
+ #include "librats/bittorrent/file_storage.h"
24
+ #include "librats/bittorrent/store_buffer.h"
25
+ #include "librats/bittorrent/torrent_info.h"
26
+ #include "librats/core/bytes.h"
27
+
28
+ #include <array>
29
+ #include <atomic>
30
+ #include <condition_variable>
31
+ #include <cstdint>
32
+ #include <functional>
33
+ #include <mutex>
34
+ #include <queue>
35
+ #include <string>
36
+ #include <thread>
37
+ #include <vector>
38
+
39
+ namespace librats::bittorrent {
40
+
41
+ /// Marshals a completion closure onto the owner's thread. If empty, handlers run
42
+ /// inline on the disk-worker thread.
43
+ using CompletionPoster = std::function<void(std::function<void()>)>;
44
+
45
+ using DiskReadHandler = std::function<void(bool ok, Bytes data)>;
46
+ using DiskWriteHandler = std::function<void(bool ok)>;
47
+ using DiskHashHandler = std::function<void(bool ok, std::array<std::uint8_t, 20> sha1)>;
48
+ using DiskCheckProgress = std::function<void(std::uint32_t piece, std::uint32_t total)>;
49
+ using DiskCheckHandler = std::function<void(Bitfield have)>;
50
+
51
+ class DiskIo {
52
+ public:
53
+ virtual ~DiskIo() = default;
54
+
55
+ /// Read @p length bytes at @p offset within @p piece.
56
+ virtual void async_read(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
57
+ DiskReadHandler handler) = 0;
58
+
59
+ /// Write a block at @p offset within @p piece.
60
+ virtual void async_write(std::uint32_t piece, std::uint32_t offset, Bytes data,
61
+ DiskWriteHandler handler) = 0;
62
+
63
+ /// SHA-1 the whole of @p piece (for hash verification by the caller).
64
+ virtual void async_hash(std::uint32_t piece, DiskHashHandler handler) = 0;
65
+
66
+ /// Verify which pieces are already complete on disk. Pieces whose bit is set
67
+ /// in @p trusted_have are accepted without re-hashing (fast resume); the rest
68
+ /// are read and hashed. @p progress fires per piece (may be null).
69
+ virtual void async_check_files(Bitfield trusted_have, DiskCheckProgress progress,
70
+ DiskCheckHandler handler) = 0;
71
+
72
+ /// Bytes of block data accepted for writing but not yet flushed to disk. The
73
+ /// Torrent uses this to apply backpressure — it stops requesting new blocks
74
+ /// while this is high — so a fast network can't grow the write queue without
75
+ /// bound when the disk is slow. Default 0 for fakes with no real queue.
76
+ virtual std::size_t queued_write_bytes() const noexcept { return 0; }
77
+
78
+ /// Stop workers and quiesce. Safe to call more than once.
79
+ virtual void stop() = 0;
80
+ };
81
+
82
+ /// Disk subsystem backed by a small pool of worker threads.
83
+ class ThreadedDiskIo final : public DiskIo {
84
+ public:
85
+ struct Config {
86
+ int num_threads = 2;
87
+ };
88
+
89
+ ThreadedDiskIo(const TorrentInfo& info, std::string save_path,
90
+ CompletionPoster poster, Config config);
91
+
92
+ // Convenience overloads (defined inline so Config{} sits in a complete-class
93
+ // context — it cannot appear in a default argument of the primary ctor).
94
+ explicit ThreadedDiskIo(const TorrentInfo& info, std::string save_path)
95
+ : ThreadedDiskIo(info, std::move(save_path), CompletionPoster{}, Config{}) {}
96
+ ThreadedDiskIo(const TorrentInfo& info, std::string save_path, CompletionPoster poster)
97
+ : ThreadedDiskIo(info, std::move(save_path), std::move(poster), Config{}) {}
98
+
99
+ ~ThreadedDiskIo() override;
100
+
101
+ ThreadedDiskIo(const ThreadedDiskIo&) = delete;
102
+ ThreadedDiskIo& operator=(const ThreadedDiskIo&) = delete;
103
+
104
+ void async_read(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
105
+ DiskReadHandler handler) override;
106
+ void async_write(std::uint32_t piece, std::uint32_t offset, Bytes data,
107
+ DiskWriteHandler handler) override;
108
+ void async_hash(std::uint32_t piece, DiskHashHandler handler) override;
109
+ void async_check_files(Bitfield trusted_have, DiskCheckProgress progress,
110
+ DiskCheckHandler handler) override;
111
+ std::size_t queued_write_bytes() const noexcept override { return queued_bytes_.load(std::memory_order_relaxed); }
112
+ void stop() override;
113
+
114
+ private:
115
+ void enqueue(std::function<void()> job);
116
+ void worker_loop();
117
+ void complete(std::function<void()> handler);
118
+
119
+ bool ensure_file(std::size_t file_index);
120
+ bool write_range(std::uint32_t piece, std::uint32_t offset, const Bytes& data);
121
+ /// Read [offset, offset+length) of @p piece into @p out. With @p use_store the
122
+ /// store-buffer is overlaid. With @p allow_short a missing/short file yields
123
+ /// false (used by check_files) instead of being treated as an error.
124
+ bool read_range(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
125
+ Bytes& out, bool use_store, bool allow_short);
126
+
127
+ std::string file_path(std::size_t file_index) const;
128
+
129
+ FileStorage files_;
130
+ Bytes piece_hashes_; ///< concatenated 20-byte SHA-1s
131
+ std::string save_path_;
132
+ CompletionPoster poster_;
133
+ StoreBuffer store_;
134
+
135
+ // File preallocation bookkeeping.
136
+ std::mutex ensure_mutex_;
137
+ std::vector<char> ensured_; ///< per-file "already sized on disk" flag
138
+
139
+ // Bytes of block data queued for writing but not yet flushed (backpressure).
140
+ std::atomic<std::size_t> queued_bytes_{0};
141
+
142
+ // Worker pool + job queue.
143
+ std::vector<std::thread> workers_;
144
+ std::queue<std::function<void()>> jobs_;
145
+ std::mutex mutex_;
146
+ std::condition_variable cv_;
147
+ bool stopping_ = false;
148
+ };
149
+
150
+ } // namespace librats::bittorrent
@@ -0,0 +1,191 @@
1
+ #include "librats/bittorrent/extensions.h"
2
+ #include "librats/bittorrent/bencode.h"
3
+ #include "librats/bittorrent/byte_io.h"
4
+
5
+ #include <cstdio>
6
+
7
+ namespace librats::bittorrent::ext {
8
+
9
+ namespace {
10
+
11
+ /// Parse "a.b.c.d" into 4 bytes. Returns false on anything that isn't dotted IPv4.
12
+ bool ipv4_to_bytes(const std::string& ip, std::uint8_t out[4]) {
13
+ unsigned a, b, c, d;
14
+ char extra;
15
+ if (std::sscanf(ip.c_str(), "%u.%u.%u.%u%c", &a, &b, &c, &d, &extra) != 4) return false;
16
+ if (a > 255 || b > 255 || c > 255 || d > 255) return false;
17
+ out[0] = std::uint8_t(a); out[1] = std::uint8_t(b);
18
+ out[2] = std::uint8_t(c); out[3] = std::uint8_t(d);
19
+ return true;
20
+ }
21
+
22
+ std::string ipv4_to_string(const std::uint8_t* b) {
23
+ char buf[16];
24
+ std::snprintf(buf, sizeof(buf), "%u.%u.%u.%u", b[0], b[1], b[2], b[3]);
25
+ return buf;
26
+ }
27
+
28
+ /// Pack peers into the 6-bytes-each compact form (4 IPv4 + 2 port BE).
29
+ std::string compact(const std::vector<PexPeer>& peers) {
30
+ std::string out;
31
+ for (const auto& p : peers) {
32
+ std::uint8_t ip[4];
33
+ if (!ipv4_to_bytes(p.ip, ip)) continue;
34
+ out.append(reinterpret_cast<const char*>(ip), 4);
35
+ std::uint8_t port[2];
36
+ write_u16_be(port, p.port);
37
+ out.append(reinterpret_cast<const char*>(port), 2);
38
+ }
39
+ return out;
40
+ }
41
+
42
+ std::vector<PexPeer> uncompact(const std::string& s) {
43
+ std::vector<PexPeer> peers;
44
+ for (std::size_t i = 0; i + 6 <= s.size(); i += 6) {
45
+ const auto* b = reinterpret_cast<const std::uint8_t*>(s.data() + i);
46
+ peers.push_back(PexPeer{ipv4_to_string(b), read_u16_be(b + 4)});
47
+ }
48
+ return peers;
49
+ }
50
+
51
+ // Length of the first bencoded value at the start of `d` (0 on malformed input).
52
+ // A small local scanner so we can split a ut_metadata header dict from the raw
53
+ // metadata block that follows it.
54
+ std::size_t bencode_value_length(const std::uint8_t* d, std::size_t n, std::size_t pos = 0) {
55
+ if (pos >= n) return 0;
56
+ const char c = char(d[pos]);
57
+ if (c == 'i') {
58
+ std::size_t e = pos + 1;
59
+ while (e < n && d[e] != 'e') ++e;
60
+ return e < n ? e + 1 - pos : 0;
61
+ }
62
+ if (c == 'l' || c == 'd') {
63
+ std::size_t p = pos + 1;
64
+ while (p < n && d[p] != 'e') {
65
+ const std::size_t len = bencode_value_length(d, n, p);
66
+ if (len == 0) return 0;
67
+ p += len;
68
+ }
69
+ return p < n ? p + 1 - pos : 0;
70
+ }
71
+ if (c >= '0' && c <= '9') {
72
+ std::size_t colon = pos;
73
+ while (colon < n && d[colon] != ':') ++colon;
74
+ if (colon >= n) return 0;
75
+ std::uint64_t len = 0;
76
+ for (std::size_t i = pos; i < colon; ++i) len = len * 10 + std::uint64_t(d[i] - '0');
77
+ const std::size_t total = (colon + 1 - pos) + len;
78
+ return (pos + total <= n) ? total : 0;
79
+ }
80
+ return 0;
81
+ }
82
+
83
+ const librats::BencodeValue* find(const librats::BencodeValue& dict, const char* key) {
84
+ return dict.find(key);
85
+ }
86
+
87
+ std::optional<std::int64_t> find_int(const librats::BencodeValue& dict, const char* key) {
88
+ const auto* v = find(dict, key);
89
+ if (v && v->is_integer()) return v->as_integer();
90
+ return std::nullopt;
91
+ }
92
+
93
+ } // namespace
94
+
95
+ Bytes encode_handshake(std::uint32_t metadata_size, std::uint16_t listen_port) {
96
+ librats::BencodeValue d = librats::BencodeValue::create_dict();
97
+ librats::BencodeValue m = librats::BencodeValue::create_dict();
98
+ m["ut_metadata"] = librats::BencodeValue(std::int64_t(kUtMetadataLocalId));
99
+ m["ut_pex"] = librats::BencodeValue(std::int64_t(kUtPexLocalId));
100
+ d["m"] = m;
101
+ if (metadata_size > 0) d["metadata_size"] = librats::BencodeValue(std::int64_t(metadata_size));
102
+ if (listen_port > 0) d["p"] = librats::BencodeValue(std::int64_t(listen_port));
103
+ d["v"] = librats::BencodeValue(std::string("librats"));
104
+ return d.encode();
105
+ }
106
+
107
+ std::optional<PeerExtensions> decode_handshake(ByteView payload) {
108
+ try {
109
+ librats::BencodeValue d = librats::BencodeDecoder::decode(payload.data(), payload.size());
110
+ if (!d.is_dict()) return std::nullopt;
111
+ PeerExtensions ext;
112
+ if (const auto* m = find(d, "m"); m && m->is_dict()) {
113
+ if (auto v = find_int(*m, "ut_metadata")) ext.ut_metadata_id = std::uint8_t(*v);
114
+ if (auto v = find_int(*m, "ut_pex")) ext.ut_pex_id = std::uint8_t(*v);
115
+ }
116
+ if (auto v = find_int(d, "metadata_size"); v && *v > 0) ext.metadata_size = std::uint32_t(*v);
117
+ if (auto v = find_int(d, "p"); v && *v > 0) ext.listen_port = std::uint16_t(*v);
118
+ return ext;
119
+ } catch (const std::exception&) {
120
+ return std::nullopt;
121
+ }
122
+ }
123
+
124
+ Bytes encode_metadata_request(std::uint32_t piece) {
125
+ librats::BencodeValue d = librats::BencodeValue::create_dict();
126
+ d["msg_type"] = librats::BencodeValue(std::int64_t(MetadataType::Request));
127
+ d["piece"] = librats::BencodeValue(std::int64_t(piece));
128
+ return d.encode();
129
+ }
130
+
131
+ Bytes encode_metadata_reject(std::uint32_t piece) {
132
+ librats::BencodeValue d = librats::BencodeValue::create_dict();
133
+ d["msg_type"] = librats::BencodeValue(std::int64_t(MetadataType::Reject));
134
+ d["piece"] = librats::BencodeValue(std::int64_t(piece));
135
+ return d.encode();
136
+ }
137
+
138
+ Bytes encode_metadata_data(std::uint32_t piece, std::uint32_t total_size, ByteView block) {
139
+ librats::BencodeValue d = librats::BencodeValue::create_dict();
140
+ d["msg_type"] = librats::BencodeValue(std::int64_t(MetadataType::Data));
141
+ d["piece"] = librats::BencodeValue(std::int64_t(piece));
142
+ d["total_size"] = librats::BencodeValue(std::int64_t(total_size));
143
+ Bytes out = d.encode();
144
+ out.insert(out.end(), block.begin(), block.end());
145
+ return out;
146
+ }
147
+
148
+ std::optional<MetadataMessage> decode_metadata(ByteView payload) {
149
+ const std::size_t header_len = bencode_value_length(payload.data(), payload.size());
150
+ if (header_len == 0) return std::nullopt;
151
+ try {
152
+ librats::BencodeValue d = librats::BencodeDecoder::decode(payload.data(), header_len);
153
+ if (!d.is_dict()) return std::nullopt;
154
+ const auto type = find_int(d, "msg_type");
155
+ const auto piece = find_int(d, "piece");
156
+ if (!type || !piece) return std::nullopt;
157
+
158
+ MetadataMessage msg;
159
+ msg.type = MetadataType(*type);
160
+ msg.piece = std::uint32_t(*piece);
161
+ if (msg.type == MetadataType::Data) {
162
+ if (auto ts = find_int(d, "total_size")) msg.total_size = std::uint32_t(*ts);
163
+ msg.block.assign(payload.begin() + std::ptrdiff_t(header_len), payload.end());
164
+ }
165
+ return msg;
166
+ } catch (const std::exception&) {
167
+ return std::nullopt;
168
+ }
169
+ }
170
+
171
+ Bytes encode_pex(const std::vector<PexPeer>& added, const std::vector<PexPeer>& dropped) {
172
+ librats::BencodeValue d = librats::BencodeValue::create_dict();
173
+ d["added"] = librats::BencodeValue(compact(added));
174
+ d["dropped"] = librats::BencodeValue(compact(dropped));
175
+ return d.encode();
176
+ }
177
+
178
+ std::optional<PexMessage> decode_pex(ByteView payload) {
179
+ try {
180
+ librats::BencodeValue d = librats::BencodeDecoder::decode(payload.data(), payload.size());
181
+ if (!d.is_dict()) return std::nullopt;
182
+ PexMessage msg;
183
+ if (const auto* a = find(d, "added"); a && a->is_string()) msg.added = uncompact(a->as_string());
184
+ if (const auto* dr = find(d, "dropped"); dr && dr->is_string()) msg.dropped = uncompact(dr->as_string());
185
+ return msg;
186
+ } catch (const std::exception&) {
187
+ return std::nullopt;
188
+ }
189
+ }
190
+
191
+ } // namespace librats::bittorrent::ext
@@ -0,0 +1,94 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file extensions.h
5
+ * @brief BEP 10 extension protocol + BEP 9 metadata-exchange message codecs.
6
+ *
7
+ * BEP 10 layers named extensions on top of the wire's `extended` message
8
+ * (id 20). After the BitTorrent handshake each side sends an *extended
9
+ * handshake* (extended id 0) carrying a bencoded `m` dict that maps extension
10
+ * names to the message ids it wants them sent under — so the id you use to send
11
+ * a peer a `ut_metadata` message is the id *that peer* advertised, while the id
12
+ * you receive it under is the one *you* advertised (our fixed kUtMetadataLocalId).
13
+ *
14
+ * BEP 9 (`ut_metadata`) fetches the info-dictionary from peers for magnet links:
15
+ * the metadata is split into 16 KiB pieces requested/served via three message
16
+ * types (request / data / reject).
17
+ *
18
+ * These functions are pure (de)serialisation — the stateful request/serve/verify
19
+ * logic lives in the Torrent. That keeps the wire format unit-testable in
20
+ * isolation.
21
+ */
22
+
23
+ #include "librats/bittorrent/types.h"
24
+ #include "librats/core/bytes.h"
25
+
26
+ #include <cstdint>
27
+ #include <optional>
28
+ #include <string>
29
+ #include <vector>
30
+
31
+ namespace librats::bittorrent::ext {
32
+
33
+ // Our local extended-message ids (what we tell peers to address us by).
34
+ constexpr std::uint8_t kUtMetadataLocalId = 1;
35
+ constexpr std::uint8_t kUtPexLocalId = 2;
36
+
37
+ constexpr char kUtMetadataName[] = "ut_metadata";
38
+ constexpr char kUtPexName[] = "ut_pex";
39
+
40
+ // ---- extended handshake (extended id 0) ----
41
+
42
+ /// Build our extended handshake. @p metadata_size is advertised only when > 0
43
+ /// (i.e. we actually hold the metadata); likewise @p listen_port when non-zero.
44
+ Bytes encode_handshake(std::uint32_t metadata_size, std::uint16_t listen_port);
45
+
46
+ struct PeerExtensions {
47
+ std::uint8_t ut_metadata_id = 0; ///< id to send ut_metadata under (0 = unsupported)
48
+ std::uint8_t ut_pex_id = 0;
49
+ std::uint32_t metadata_size = 0; ///< peer's info-dict size, if advertised
50
+ std::uint16_t listen_port = 0;
51
+ };
52
+
53
+ /// Parse a peer's extended handshake. nullopt if it isn't a valid bencoded dict.
54
+ std::optional<PeerExtensions> decode_handshake(ByteView payload);
55
+
56
+ // ---- ut_metadata (BEP 9) ----
57
+
58
+ enum class MetadataType : std::uint8_t { Request = 0, Data = 1, Reject = 2 };
59
+
60
+ Bytes encode_metadata_request(std::uint32_t piece);
61
+ Bytes encode_metadata_reject(std::uint32_t piece);
62
+ /// A `data` message: the bencoded header followed by the raw metadata @p block.
63
+ Bytes encode_metadata_data(std::uint32_t piece, std::uint32_t total_size, ByteView block);
64
+
65
+ struct MetadataMessage {
66
+ MetadataType type = MetadataType::Request;
67
+ std::uint32_t piece = 0;
68
+ std::uint32_t total_size = 0; ///< only set on Data
69
+ Bytes block; ///< only set on Data (the raw 16 KiB slice)
70
+ };
71
+
72
+ /// Parse a ut_metadata message (header dict + optional trailing block). nullopt
73
+ /// if malformed.
74
+ std::optional<MetadataMessage> decode_metadata(ByteView payload);
75
+
76
+ // ---- ut_pex (BEP 11) ----
77
+
78
+ struct PexPeer {
79
+ std::string ip;
80
+ std::uint16_t port = 0;
81
+ };
82
+
83
+ /// Encode an IPv4 peer-exchange message (`added`/`dropped` compact lists).
84
+ Bytes encode_pex(const std::vector<PexPeer>& added, const std::vector<PexPeer>& dropped);
85
+
86
+ struct PexMessage {
87
+ std::vector<PexPeer> added;
88
+ std::vector<PexPeer> dropped;
89
+ };
90
+
91
+ /// Parse a ut_pex message. nullopt if malformed. (IPv4 `added`/`dropped` only.)
92
+ std::optional<PexMessage> decode_pex(ByteView payload);
93
+
94
+ } // namespace librats::bittorrent::ext