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,374 @@
1
+ #include "librats/bittorrent/tracker.h"
2
+
3
+ #include "librats/bittorrent/bencode.h"
4
+ #include "librats/bittorrent/byte_io.h"
5
+ #include "librats/bittorrent/log.h"
6
+ #include "librats/core/socket.h"
7
+ #include "librats/util/network_utils.h"
8
+
9
+ #include <algorithm>
10
+ #include <cctype>
11
+ #include <cstring>
12
+ #include <iomanip>
13
+ #include <random>
14
+ #include <sstream>
15
+ #include <thread>
16
+
17
+ namespace librats::bittorrent {
18
+
19
+ namespace {
20
+
21
+ // UDP BEP 15 constants.
22
+ constexpr std::int64_t kUdpMagic = 0x41727101980LL;
23
+ constexpr std::uint32_t kActionConnect = 0;
24
+ constexpr std::uint32_t kActionAnnounce = 1;
25
+ constexpr std::uint32_t kActionError = 3;
26
+
27
+ std::uint32_t random_u32() {
28
+ thread_local std::mt19937 gen(std::random_device{}());
29
+ return std::uniform_int_distribution<std::uint32_t>{}(gen);
30
+ }
31
+
32
+ // Parse a TCP/UDP port out of an untrusted tracker URL. Returns 0 on anything
33
+ // that is not a plain 1..65535 decimal number. std::stoi must NOT be used here:
34
+ // a hostile ".torrent"/magnet `tr=udp://host:notaport` would throw
35
+ // std::invalid_argument out of the detached announce worker → std::terminate.
36
+ std::uint16_t parse_port(const std::string& s) {
37
+ if (s.empty()) return 0;
38
+ std::uint32_t v = 0;
39
+ for (const char c : s) {
40
+ if (c < '0' || c > '9') return 0;
41
+ v = v * 10 + std::uint32_t(c - '0');
42
+ if (v > 65535) return 0;
43
+ }
44
+ return std::uint16_t(v);
45
+ }
46
+
47
+ const char* http_event(TrackerEvent e) {
48
+ switch (e) {
49
+ case TrackerEvent::Started: return "started";
50
+ case TrackerEvent::Stopped: return "stopped";
51
+ case TrackerEvent::Completed: return "completed";
52
+ default: return "";
53
+ }
54
+ }
55
+
56
+ // UDP event codes differ from our enum order, so map explicitly.
57
+ std::uint32_t udp_event(TrackerEvent e) {
58
+ switch (e) {
59
+ case TrackerEvent::Completed: return 1;
60
+ case TrackerEvent::Started: return 2;
61
+ case TrackerEvent::Stopped: return 3;
62
+ default: return 0;
63
+ }
64
+ }
65
+
66
+ std::string url_encode_binary(const std::uint8_t* data, std::size_t len) {
67
+ std::ostringstream o;
68
+ o.fill('0');
69
+ o << std::hex;
70
+ for (std::size_t i = 0; i < len; ++i) {
71
+ const std::uint8_t c = data[i];
72
+ if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') o << char(c);
73
+ else o << '%' << std::setw(2) << int(c);
74
+ }
75
+ return o.str();
76
+ }
77
+
78
+ std::vector<Address> parse_compact_peers(const std::string& data) {
79
+ std::vector<Address> peers;
80
+ for (std::size_t i = 0; i + 6 <= data.size(); i += 6) {
81
+ const auto* b = reinterpret_cast<const std::uint8_t*>(data.data() + i);
82
+ std::ostringstream ip;
83
+ ip << int(b[0]) << '.' << int(b[1]) << '.' << int(b[2]) << '.' << int(b[3]);
84
+ peers.emplace_back(ip.str(), read_u16_be(b + 4));
85
+ }
86
+ return peers;
87
+ }
88
+
89
+ std::vector<Address> parse_dict_peers(const librats::BencodeValue& list) {
90
+ std::vector<Address> peers;
91
+ for (std::size_t i = 0; i < list.size(); ++i) {
92
+ const auto& d = list[i];
93
+ if (!d.is_dict() || !d.has_key("ip") || !d.has_key("port")) continue;
94
+ const std::uint16_t port = std::uint16_t(d["port"].as_integer());
95
+ if (port == 0) continue;
96
+ // BEP 3 dictionary model: "ip" may be a dotted-quad, an IPv6 literal, OR a
97
+ // DNS name. Address is strictly numeric, so a name must be resolved here
98
+ // (we already run on a blocking announce worker) — feeding it to the numeric
99
+ // Address ctor would assert in debug and silently drop the peer in release.
100
+ const std::string& host = d["ip"].as_string();
101
+ auto ip = IpAddress::parse(host);
102
+ if (!ip) {
103
+ std::string resolved = network_utils::resolve_hostname(host);
104
+ if (resolved.empty()) resolved = network_utils::resolve_hostname_v6(host);
105
+ ip = IpAddress::parse(resolved);
106
+ }
107
+ if (ip) peers.emplace_back(*ip, port);
108
+ }
109
+ return peers;
110
+ }
111
+
112
+ /// Blocking HTTP/1.0 GET; returns the response body (headers stripped).
113
+ Bytes http_get(const std::string& url, int timeout_ms) {
114
+ const std::size_t scheme = url.find("://");
115
+ if (scheme == std::string::npos) return {};
116
+ const std::string proto = url.substr(0, scheme);
117
+ const std::size_t host_start = scheme + 3;
118
+ const std::size_t path_start = url.find('/', host_start);
119
+
120
+ std::string host_port = url.substr(host_start, path_start == std::string::npos ? std::string::npos
121
+ : path_start - host_start);
122
+ std::string path = (path_start == std::string::npos) ? "/" : url.substr(path_start);
123
+
124
+ std::uint16_t port = (proto == "https") ? 443 : 80;
125
+ if (const std::size_t colon = host_port.find(':'); colon != std::string::npos) {
126
+ const std::uint16_t parsed = parse_port(host_port.substr(colon + 1));
127
+ if (parsed == 0) return {}; // malformed port — fail the announce, never crash
128
+ port = parsed;
129
+ host_port = host_port.substr(0, colon);
130
+ }
131
+
132
+ socket_t sock = create_tcp_client(host_port, port, timeout_ms);
133
+ if (!is_valid_socket(sock)) return {};
134
+
135
+ std::ostringstream req;
136
+ req << "GET " << path << " HTTP/1.0\r\nHost: " << host_port
137
+ << "\r\nUser-Agent: librats\r\nAccept: */*\r\nConnection: close\r\n\r\n";
138
+ if (send_tcp_string(sock, req.str()) <= 0) { close_socket(sock); return {}; }
139
+
140
+ Bytes data;
141
+ for (;;) {
142
+ Bytes chunk = receive_tcp_data(sock, 4096);
143
+ if (chunk.empty()) break;
144
+ data.insert(data.end(), chunk.begin(), chunk.end());
145
+ }
146
+ close_socket(sock);
147
+
148
+ // Strip headers (everything up to and including the blank line).
149
+ static const std::uint8_t sep[4] = {'\r', '\n', '\r', '\n'};
150
+ for (std::size_t i = 0; i + 4 <= data.size(); ++i) {
151
+ if (std::memcmp(data.data() + i, sep, 4) == 0)
152
+ return Bytes(data.begin() + std::ptrdiff_t(i + 4), data.end());
153
+ }
154
+ return {};
155
+ }
156
+
157
+ // Wait for a UDP datagram for up to @p total_timeout_ms, but in short slices so a
158
+ // pending cancellation (e.g. shutdown) aborts the wait within one slice instead
159
+ // of blocking for the whole timeout against an unresponsive tracker. Returns the
160
+ // datagram, or empty on timeout / cancellation.
161
+ Bytes receive_udp_sliced(socket_t sock, std::size_t maxlen, Address& sender, int total_timeout_ms,
162
+ const TrackerCancelPredicate& cancelled) {
163
+ if (!cancelled) {
164
+ // No cancellation wanted — one plain blocking wait (fast path).
165
+ return receive_udp_data(sock, maxlen, sender, total_timeout_ms);
166
+ }
167
+ constexpr int kSliceMs = 200;
168
+ int elapsed = 0;
169
+ while (elapsed < total_timeout_ms) {
170
+ if (cancelled()) return {};
171
+ const int slice = (std::min)(kSliceMs, total_timeout_ms - elapsed);
172
+ Bytes data = receive_udp_data(sock, maxlen, sender, slice);
173
+ if (!data.empty()) return data;
174
+ elapsed += slice;
175
+ }
176
+ return {};
177
+ }
178
+
179
+ TrackerResponse announce_http(const std::string& url, const TrackerRequest& req, int timeout_ms,
180
+ const TrackerCancelPredicate& cancelled) {
181
+ TrackerResponse out;
182
+ if (cancelled && cancelled()) { out.failure_reason = "cancelled"; return out; }
183
+ const Bytes body = http_get(tracker_detail::build_http_announce_url(url, req), timeout_ms);
184
+ if (body.empty()) { out.failure_reason = "no response"; return out; }
185
+ return tracker_detail::parse_http_response(body);
186
+ }
187
+
188
+ TrackerResponse announce_udp(const std::string& url, const TrackerRequest& req, int timeout_ms,
189
+ const TrackerCancelPredicate& cancelled) {
190
+ TrackerResponse out;
191
+ if (cancelled && cancelled()) { out.failure_reason = "cancelled"; return out; }
192
+
193
+ // Parse udp://host:port[/...]
194
+ std::string rest = url.substr(6);
195
+ if (const std::size_t slash = rest.find('/'); slash != std::string::npos) rest = rest.substr(0, slash);
196
+ const std::size_t colon = rest.find(':');
197
+ if (colon == std::string::npos) { out.failure_reason = "bad udp url"; return out; }
198
+ const std::string host = rest.substr(0, colon);
199
+ const std::uint16_t port = parse_port(rest.substr(colon + 1));
200
+ if (port == 0) { out.failure_reason = "bad udp port"; return out; }
201
+
202
+ socket_t sock = create_udp_socket(0, "", AddressFamily::IPv4);
203
+ if (!is_valid_socket(sock)) { out.failure_reason = "socket"; return out; }
204
+ struct Closer { socket_t s; ~Closer() { if (is_valid_socket(s)) close_socket(s); } } closer{sock};
205
+
206
+ // 1) connect
207
+ const std::uint32_t tid1 = random_u32();
208
+ Bytes creq(16);
209
+ write_u64_be(creq.data(), std::uint64_t(kUdpMagic));
210
+ write_u32_be(creq.data() + 8, kActionConnect);
211
+ write_u32_be(creq.data() + 12, tid1);
212
+ if (send_udp_data(sock, creq, host, port, AddressFamily::IPv4) <= 0) { out.failure_reason = "send"; return out; }
213
+
214
+ Address sender;
215
+ Bytes cresp = receive_udp_sliced(sock, 2048, sender, timeout_ms, cancelled);
216
+ if (cresp.size() < 16 || read_u32_be(cresp.data()) != kActionConnect ||
217
+ read_u32_be(cresp.data() + 4) != tid1) {
218
+ out.failure_reason = (cancelled && cancelled()) ? "cancelled" : "connect failed";
219
+ return out;
220
+ }
221
+ const std::uint64_t conn_id = read_u64_be(cresp.data() + 8);
222
+
223
+ // 2) announce
224
+ const std::uint32_t tid2 = random_u32();
225
+ Bytes areq(98);
226
+ write_u64_be(areq.data(), conn_id);
227
+ write_u32_be(areq.data() + 8, kActionAnnounce);
228
+ write_u32_be(areq.data() + 12, tid2);
229
+ std::memcpy(areq.data() + 16, req.info_hash.data(), 20);
230
+ std::memcpy(areq.data() + 36, req.peer_id.data(), 20);
231
+ write_u64_be(areq.data() + 56, req.downloaded);
232
+ write_u64_be(areq.data() + 64, req.left);
233
+ write_u64_be(areq.data() + 72, req.uploaded);
234
+ write_u32_be(areq.data() + 80, udp_event(req.event));
235
+ write_u32_be(areq.data() + 84, 0); // our IP — let the tracker use the source
236
+ write_u32_be(areq.data() + 88, random_u32()); // key
237
+ write_u32_be(areq.data() + 92, std::uint32_t(req.numwant));
238
+ write_u16_be(areq.data() + 96, req.port);
239
+ if (send_udp_data(sock, areq, host, port, AddressFamily::IPv4) <= 0) { out.failure_reason = "send"; return out; }
240
+
241
+ Bytes resp = receive_udp_sliced(sock, 2048, sender, timeout_ms, cancelled);
242
+ if (resp.size() < 8) {
243
+ out.failure_reason = (cancelled && cancelled()) ? "cancelled" : "no response";
244
+ return out;
245
+ }
246
+ const std::uint32_t action = read_u32_be(resp.data());
247
+ if (action == kActionError) {
248
+ out.failure_reason.assign(resp.begin() + 8, resp.end());
249
+ return out;
250
+ }
251
+ if (action != kActionAnnounce || read_u32_be(resp.data() + 4) != tid2 || resp.size() < 20) {
252
+ out.failure_reason = "bad announce response";
253
+ return out;
254
+ }
255
+
256
+ out.interval = read_u32_be(resp.data() + 8);
257
+ out.incomplete = read_u32_be(resp.data() + 12);
258
+ out.complete = read_u32_be(resp.data() + 16);
259
+ for (std::size_t off = 20; off + 6 <= resp.size(); off += 6) {
260
+ const std::uint8_t* b = resp.data() + off;
261
+ std::ostringstream ip;
262
+ ip << int(b[0]) << '.' << int(b[1]) << '.' << int(b[2]) << '.' << int(b[3]);
263
+ out.peers.emplace_back(ip.str(), read_u16_be(b + 4));
264
+ }
265
+ out.success = true;
266
+ return out;
267
+ }
268
+
269
+ } // namespace
270
+
271
+ namespace tracker_detail {
272
+
273
+ std::string build_http_announce_url(const std::string& base, const TrackerRequest& req) {
274
+ std::ostringstream u;
275
+ u << base << (base.find('?') == std::string::npos ? '?' : '&');
276
+ u << "info_hash=" << url_encode_binary(req.info_hash.data(), req.info_hash.size());
277
+ u << "&peer_id=" << url_encode_binary(req.peer_id.data(), req.peer_id.size());
278
+ u << "&port=" << req.port;
279
+ u << "&uploaded=" << req.uploaded;
280
+ u << "&downloaded=" << req.downloaded;
281
+ u << "&left=" << req.left;
282
+ u << "&numwant=" << req.numwant;
283
+ u << "&compact=1";
284
+ if (req.event != TrackerEvent::None) u << "&event=" << http_event(req.event);
285
+ return u.str();
286
+ }
287
+
288
+ TrackerResponse parse_http_response(const Bytes& body) {
289
+ TrackerResponse out;
290
+ try {
291
+ librats::BencodeValue d = librats::BencodeDecoder::decode(body.data(), body.size());
292
+ if (!d.is_dict()) { out.failure_reason = "not a dict"; return out; }
293
+ if (d.has_key("failure reason")) { out.failure_reason = d["failure reason"].as_string(); return out; }
294
+ if (d.has_key("interval")) out.interval = std::uint32_t(d["interval"].as_integer());
295
+ if (d.has_key("min interval")) out.min_interval = std::uint32_t(d["min interval"].as_integer());
296
+ if (d.has_key("complete")) out.complete = std::uint32_t(d["complete"].as_integer());
297
+ if (d.has_key("incomplete")) out.incomplete = std::uint32_t(d["incomplete"].as_integer());
298
+ if (d.has_key("peers")) {
299
+ const auto& p = d["peers"];
300
+ if (p.is_string()) out.peers = parse_compact_peers(p.as_string());
301
+ else if (p.is_list()) out.peers = parse_dict_peers(p);
302
+ }
303
+ out.success = true;
304
+ } catch (const std::exception& e) {
305
+ out.failure_reason = e.what();
306
+ }
307
+ return out;
308
+ }
309
+
310
+ } // namespace tracker_detail
311
+
312
+ TrackerResponse announce_to_tracker(const std::string& url, const TrackerRequest& req, int timeout_ms,
313
+ const TrackerCancelPredicate& cancelled) {
314
+ if (url.rfind("udp://", 0) == 0) return announce_udp(url, req, timeout_ms, cancelled);
315
+ // We have no TLS for trackers yet. Skip https:// rather than speaking cleartext
316
+ // HTTP to port 443 — that never works and would leak info_hash/peer_id in the
317
+ // clear (H11). Re-enable once a real TLS stream is wired in.
318
+ if (url.rfind("https://", 0) == 0) {
319
+ TrackerResponse out; out.failure_reason = "https trackers not supported (no TLS)";
320
+ return out;
321
+ }
322
+ if (url.rfind("http://", 0) == 0) return announce_http(url, req, timeout_ms, cancelled);
323
+ TrackerResponse out;
324
+ out.failure_reason = "unsupported tracker scheme";
325
+ return out;
326
+ }
327
+
328
+ // ---- TrackerAnnouncer ----
329
+
330
+ TrackerAnnouncer::TrackerAnnouncer(std::vector<std::string> trackers, Poster poster, int timeout_ms)
331
+ : trackers_(std::move(trackers)), poster_(std::move(poster)), timeout_ms_(timeout_ms) {}
332
+
333
+ TrackerAnnouncer::~TrackerAnnouncer() { stop(); }
334
+
335
+ void TrackerAnnouncer::announce(const TrackerRequest& req, PeerCallback on_peers) {
336
+ for (const std::string& url : trackers_) {
337
+ {
338
+ std::lock_guard<std::mutex> lk(mutex_);
339
+ if (stopping_) return;
340
+ ++inflight_;
341
+ }
342
+ std::thread([this, url, req, on_peers] {
343
+ TrackerResponse resp = announce_to_tracker(url, req, timeout_ms_);
344
+ if (resp.success)
345
+ LOG_DEBUG("bt.tracker", "← " << url << ": " << resp.peers.size() << " peers, interval "
346
+ << resp.interval << "s");
347
+ else
348
+ LOG_DEBUG("bt.tracker", "✗ " << url << ": " << resp.failure_reason);
349
+ {
350
+ std::lock_guard<std::mutex> lk(mutex_);
351
+ if (!stopping_ && resp.success && poster_) {
352
+ // Honour the tracker's requested re-announce cadence (clamped to a
353
+ // sane 1 min..1 h) so we don't get rate-limited/banned (H13).
354
+ std::uint32_t interval = (std::max)(resp.interval, resp.min_interval);
355
+ if (interval == 0) interval = 1800;
356
+ interval = std::min<std::uint32_t>(std::max<std::uint32_t>(interval, 60), 3600);
357
+ poster_([on_peers, peers = resp.peers, interval] { on_peers(peers, interval); });
358
+ }
359
+ --inflight_;
360
+ // Notify while still holding the lock: once inflight_ hits zero a waiting
361
+ // stop() may return and the announcer be destroyed, taking drain_cv_ with it.
362
+ drain_cv_.notify_all();
363
+ }
364
+ }).detach();
365
+ }
366
+ }
367
+
368
+ void TrackerAnnouncer::stop() {
369
+ std::unique_lock<std::mutex> lk(mutex_);
370
+ stopping_ = true;
371
+ drain_cv_.wait(lk, [this] { return inflight_ == 0; });
372
+ }
373
+
374
+ } // namespace librats::bittorrent
@@ -0,0 +1,108 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file tracker.h
5
+ * @brief Tracker announces (BEP 3 over HTTP, BEP 15 over UDP) and the per-torrent
6
+ * announcer that drives them.
7
+ *
8
+ * A tracker is a directory: you periodically POST your progress and it hands back
9
+ * a list of peers. The wire work is blocking (a short HTTP request or a two-step
10
+ * UDP connect→announce), so announce_to_tracker() is a self-contained blocking
11
+ * call with no shared state — easy to test and safe to run off-thread.
12
+ *
13
+ * TrackerAnnouncer is what a Torrent owns: it fires announces to every tracker on
14
+ * background threads and delivers discovered peers back through a poster (onto the
15
+ * reactor), exactly like the disk subsystem. stop() joins any in-flight announce
16
+ * so a torrent never tears down underneath one.
17
+ */
18
+
19
+ #include "librats/bittorrent/types.h"
20
+ #include "librats/core/address.h"
21
+ #include "librats/core/bytes.h"
22
+
23
+ #include <condition_variable>
24
+ #include <cstdint>
25
+ #include <functional>
26
+ #include <mutex>
27
+ #include <string>
28
+ #include <vector>
29
+
30
+ namespace librats::bittorrent {
31
+
32
+ enum class TrackerEvent { None, Started, Stopped, Completed };
33
+
34
+ struct TrackerRequest {
35
+ InfoHash info_hash{};
36
+ PeerId peer_id{};
37
+ std::uint16_t port = 0;
38
+ std::uint64_t uploaded = 0;
39
+ std::uint64_t downloaded = 0;
40
+ std::uint64_t left = 0;
41
+ TrackerEvent event = TrackerEvent::None;
42
+ int numwant = 50;
43
+ };
44
+
45
+ struct TrackerResponse {
46
+ bool success = false;
47
+ std::string failure_reason;
48
+ std::uint32_t interval = 1800; ///< seconds until the next announce
49
+ std::uint32_t min_interval = 0;
50
+ std::uint32_t complete = 0; ///< seeders
51
+ std::uint32_t incomplete = 0; ///< leechers
52
+ std::vector<Address> peers;
53
+ };
54
+
55
+ /// Polled by the blocking announce so a caller can abort it early (e.g. on
56
+ /// shutdown): when it returns true the announce bails within ~one poll slice
57
+ /// instead of blocking for the full timeout. An empty predicate never cancels.
58
+ using TrackerCancelPredicate = std::function<bool()>;
59
+
60
+ /// Perform one blocking announce to @p url (http/https/udp). Self-contained: it
61
+ /// opens, uses and closes its own socket and shares no state with anything.
62
+ /// @p cancelled, if set, is polled during the wait so the call can be aborted
63
+ /// promptly (the blocking receive is done in short slices between polls).
64
+ TrackerResponse announce_to_tracker(const std::string& url, const TrackerRequest& req,
65
+ int timeout_ms = 10000,
66
+ const TrackerCancelPredicate& cancelled = {});
67
+
68
+ /// Drives announces to a torrent's trackers from background threads, delivering
69
+ /// peers via a poster (the reactor). Owned by one Torrent.
70
+ class TrackerAnnouncer {
71
+ public:
72
+ using Poster = std::function<void(std::function<void()>)>;
73
+ /// Delivers a tracker's response: discovered peers and the interval (seconds)
74
+ /// the tracker wants before the next announce (already max'd with min-interval).
75
+ using PeerCallback = std::function<void(const std::vector<Address>& peers, std::uint32_t interval)>;
76
+
77
+ TrackerAnnouncer(std::vector<std::string> trackers, Poster poster, int timeout_ms = 10000);
78
+ ~TrackerAnnouncer();
79
+
80
+ TrackerAnnouncer(const TrackerAnnouncer&) = delete;
81
+ TrackerAnnouncer& operator=(const TrackerAnnouncer&) = delete;
82
+
83
+ /// Announce to every tracker (each on its own worker thread); @p on_peers is
84
+ /// posted onto the reactor for each tracker that returns peers.
85
+ void announce(const TrackerRequest& req, PeerCallback on_peers);
86
+ /// Wait for any in-flight announce to finish. Idempotent; called by the dtor.
87
+ void stop();
88
+
89
+ std::size_t tracker_count() const noexcept { return trackers_.size(); }
90
+
91
+ private:
92
+ std::vector<std::string> trackers_;
93
+ Poster poster_;
94
+ int timeout_ms_;
95
+
96
+ std::mutex mutex_;
97
+ std::condition_variable drain_cv_;
98
+ int inflight_ = 0;
99
+ bool stopping_ = false;
100
+ };
101
+
102
+ // Exposed for unit testing the HTTP wire format without a live tracker.
103
+ namespace tracker_detail {
104
+ std::string build_http_announce_url(const std::string& base, const TrackerRequest& req);
105
+ TrackerResponse parse_http_response(const Bytes& body);
106
+ } // namespace tracker_detail
107
+
108
+ } // namespace librats::bittorrent