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,206 @@
1
+ #include "librats/bittorrent/types.h"
2
+
3
+ #include <cstdio>
4
+ #include <cstring>
5
+ #include <random>
6
+
7
+ namespace librats::bittorrent {
8
+
9
+ // =============================================================================
10
+ // Hex / identity helpers
11
+ // =============================================================================
12
+
13
+ std::string to_hex(const std::uint8_t* data, std::size_t len) {
14
+ static const char digits[] = "0123456789abcdef";
15
+ std::string out;
16
+ out.resize(len * 2);
17
+ for (std::size_t i = 0; i < len; ++i) {
18
+ out[i * 2] = digits[data[i] >> 4];
19
+ out[i * 2 + 1] = digits[data[i] & 0x0F];
20
+ }
21
+ return out;
22
+ }
23
+
24
+ std::optional<InfoHash> info_hash_from_hex(const std::string& hex) {
25
+ if (hex.size() != kInfoHashSize * 2) return std::nullopt;
26
+
27
+ auto nibble = [](char c) -> int {
28
+ if (c >= '0' && c <= '9') return c - '0';
29
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
30
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
31
+ return -1;
32
+ };
33
+
34
+ InfoHash out{};
35
+ for (std::size_t i = 0; i < kInfoHashSize; ++i) {
36
+ const int hi = nibble(hex[i * 2]);
37
+ const int lo = nibble(hex[i * 2 + 1]);
38
+ if (hi < 0 || lo < 0) return std::nullopt;
39
+ out[i] = std::uint8_t(hi << 4 | lo);
40
+ }
41
+ return out;
42
+ }
43
+
44
+ bool is_all_zero(const std::array<std::uint8_t, 20>& id) noexcept {
45
+ for (std::uint8_t b : id) if (b != 0) return false;
46
+ return true;
47
+ }
48
+
49
+ PeerId generate_peer_id(const std::string& client_prefix) {
50
+ PeerId id{};
51
+ const std::size_t prefix_len = (std::min)(client_prefix.size(), kPeerIdSize);
52
+ for (std::size_t i = 0; i < prefix_len; ++i) id[i] = std::uint8_t(client_prefix[i]);
53
+
54
+ std::random_device rd;
55
+ std::mt19937 gen(rd());
56
+ std::uniform_int_distribution<int> dist(0, 255);
57
+ for (std::size_t i = prefix_len; i < kPeerIdSize; ++i) id[i] = std::uint8_t(dist(gen));
58
+
59
+ return id;
60
+ }
61
+
62
+ // =============================================================================
63
+ // Client identification (BEP 20)
64
+ //
65
+ // Ported from the prior librats implementation: recognises the Azureus,
66
+ // Shadow and Mainline encodings plus a long table of non-standard clients.
67
+ // =============================================================================
68
+
69
+ namespace {
70
+
71
+ int decode_version_digit(std::uint8_t c) {
72
+ if (c >= '0' && c <= '9') return c - '0';
73
+ if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
74
+ if (c >= 'a' && c <= 'z') return c - 'a' + 10;
75
+ return 0;
76
+ }
77
+
78
+ const char* lookup_az_client(const char* code) {
79
+ struct Entry { const char* code; const char* name; };
80
+ static const Entry entries[] = {
81
+ {"7T","aTorrent"},{"AB","AnyEvent BitTorrent"},{"AG","Ares"},{"AR","Arctic Torrent"},
82
+ {"AT","Artemis"},{"AV","Avicora"},{"AX","BitPump"},{"AZ","Azureus"},{"A~","Ares"},
83
+ {"BB","BitBuddy"},{"BC","BitComet"},{"BE","baretorrent"},{"BF","Bitflu"},{"BG","BTG"},
84
+ {"BI","BiglyBT"},{"BL","BitBlinder"},{"BP","BitTorrent Pro"},{"BR","BitRocket"},
85
+ {"BS","BTSlave"},{"BT","BitTorrent"},{"BU","BigUp"},{"BW","BitWombat"},{"BX","BittorrentX"},
86
+ {"CD","Enhanced CTorrent"},{"CT","CTorrent"},{"DE","Deluge"},{"DP","Propagate Data Client"},
87
+ {"EB","EBit"},{"ES","electric sheep"},{"FC","FileCroc"},{"FT","FoxTorrent"},{"FW","FrostWire"},
88
+ {"FX","Freebox BitTorrent"},{"GS","GSTorrent"},{"HK","Hekate"},{"HL","Halite"},{"HN","Hydranode"},
89
+ {"IL","iLivid"},{"KC","Koinonein"},{"KG","KGet"},{"KT","KTorrent"},{"LC","LeechCraft"},
90
+ {"LH","LH-ABC"},{"LK","Linkage"},{"LP","lphant"},{"LR","librats"},{"LT","libtorrent"},
91
+ {"LW","Limewire"},{"ML","MLDonkey"},{"MO","Mono Torrent"},{"MP","MooPolice"},{"MR","Miro"},
92
+ {"MT","Moonlight Torrent"},{"NX","Net Transport"},{"OS","OneSwarm"},{"OT","OmegaTorrent"},
93
+ {"PD","Pando"},{"QD","QQDownload"},{"QT","Qt 4"},{"RT","Retriever"},{"RZ","RezTorrent"},
94
+ {"SB","Swiftbit"},{"SD","Xunlei"},{"SK","spark"},{"SN","ShareNet"},{"SS","SwarmScope"},
95
+ {"ST","SymTorrent"},{"SZ","Shareaza"},{"S~","Shareaza (beta)"},{"TB","Torch"},{"TL","Tribler"},
96
+ {"TN","Torrent.NET"},{"TR","Transmission"},{"TS","TorrentStorm"},{"TT","TuoTu"},{"UL","uLeecher"},
97
+ {"UM","uTorrent Mac"},{"UT","uTorrent"},{"VG","Vagaa"},{"WT","BitLet"},{"WY","FireTorrent"},
98
+ {"XF","Xfplay"},{"XL","Xunlei"},{"XS","XSwifter"},{"XT","XanTorrent"},{"XX","Xtorrent"},
99
+ {"ZO","Zona"},{"ZT","ZipTorrent"},{"lt","rTorrent"},{"pX","pHoeniX"},{"qB","qBittorrent"},
100
+ {"st","SharkTorrent"},
101
+ };
102
+ for (const auto& e : entries) {
103
+ if (e.code[0] == code[0] && e.code[1] == code[1]) return e.name;
104
+ }
105
+ return nullptr;
106
+ }
107
+
108
+ const char* lookup_generic_client(const std::uint8_t* id) {
109
+ struct Entry { int offset; const char* pattern; const char* name; };
110
+ static const Entry entries[] = {
111
+ {0,"Deadman Walking-","Deadman"},{5,"Azureus","Azureus 2.0.3.2"},{0,"DansClient","XanTorrent"},
112
+ {4,"btfans","SimpleBT"},{0,"PRC.P---","Bittorrent Plus! II"},{0,"P87.P---","Bittorrent Plus!"},
113
+ {0,"S587Plus","Bittorrent Plus!"},{0,"martini","Martini Man"},{0,"Plus---","Bittorrent Plus"},
114
+ {0,"turbobt","TurboBT"},{0,"a00---0","Swarmy"},{0,"a02---0","Swarmy"},{0,"T00---0","Teeweety"},
115
+ {0,"BTDWV-","Deadman Walking"},{2,"BS","BitSpirit"},{0,"-SP","BitSpirit 3.6"},{0,"Pando-","Pando"},
116
+ {0,"LIME","LimeWire"},{0,"btuga","BTugaXP"},{0,"oernu","BTugaXP"},{0,"Mbrst","Burst!"},
117
+ {0,"PEERAPP","PeerApp"},{0,"Plus","Plus!"},{0,"-Qt-","Qt"},{0,"exbc","BitComet"},
118
+ {0,"DNA","BitTorrent DNA"},{0,"-G3","G3 Torrent"},{0,"-FG","FlashGet"},{0,"-ML","MLdonkey"},
119
+ {0,"-MG","Media Get"},{0,"XBT","XBT"},{0,"OP","Opera"},{2,"RS","Rufus"},{0,"AZ2500BT","BitTyrant"},
120
+ {0,"btpd/","BitTorrent Protocol Daemon"},{0,"TIX","Tixati"},{0,"QVOD","Qvod"},
121
+ };
122
+ for (const auto& e : entries) {
123
+ const std::size_t len = std::strlen(e.pattern);
124
+ if (std::size_t(e.offset) + len <= kPeerIdSize &&
125
+ std::memcmp(id + e.offset, e.pattern, len) == 0) {
126
+ return e.name;
127
+ }
128
+ }
129
+ return nullptr;
130
+ }
131
+
132
+ } // namespace
133
+
134
+ std::string identify_client(const PeerId& id) {
135
+ if (is_all_zero(id)) return "Unknown";
136
+
137
+ if (const char* generic = lookup_generic_client(id.data())) return generic;
138
+
139
+ // Bits on Wheels special case: "-BOW...-"
140
+ if (id[0] == '-' && id[1] == 'B' && id[2] == 'O' && id[3] == 'W' && id[7] == '-') {
141
+ return "Bits on Wheels " + std::string(reinterpret_cast<const char*>(id.data()) + 4, 3);
142
+ }
143
+
144
+ // Azureus-style: -XX1234-
145
+ if (id[0] == '-' && id[7] == '-' &&
146
+ id[3] >= '0' && id[4] >= '0' && id[5] >= '0' && id[6] >= '0') {
147
+ const char code[3] = {char(id[1]), char(id[2]), '\0'};
148
+ const int v1 = decode_version_digit(id[3]);
149
+ const int v2 = decode_version_digit(id[4]);
150
+ const int v3 = decode_version_digit(id[5]);
151
+ const int v4 = decode_version_digit(id[6]);
152
+
153
+ const char* name = lookup_az_client(code);
154
+ std::string client = name ? name : std::string("Unknown (") + code + ")";
155
+ std::string version = std::to_string(v1) + "." + std::to_string(v2) + "." + std::to_string(v3);
156
+ if (v4 != 0) version += "." + std::to_string(v4);
157
+ return client + " " + version;
158
+ }
159
+
160
+ // Shadow-style: X + 3 version chars + "--", and Mainline-style: M1-2-3--
161
+ if ((id[0] >= 'A' && id[0] <= 'Z') || (id[0] >= 'a' && id[0] <= 'z')) {
162
+ if (id[4] == '-' && id[5] == '-' && id[1] >= '0' && id[2] >= '0' && id[3] >= '0') {
163
+ const char* name = nullptr;
164
+ switch (char(id[0])) {
165
+ case 'A': name = "ABC"; break;
166
+ case 'M': name = "Mainline"; break;
167
+ case 'O': name = "Osprey Permaseed"; break;
168
+ case 'Q': name = "BTQueue"; break;
169
+ case 'R': name = "Tribler"; break;
170
+ case 'S': name = "Shadow"; break;
171
+ case 'T': name = "BitTornado"; break;
172
+ case 'U': name = "UPnP"; break;
173
+ default: break;
174
+ }
175
+ if (name) {
176
+ return std::string(name) + " " + std::to_string(decode_version_digit(id[1])) + "." +
177
+ std::to_string(decode_version_digit(id[2])) + "." +
178
+ std::to_string(decode_version_digit(id[3]));
179
+ }
180
+ }
181
+
182
+ char ids[21];
183
+ std::memcpy(ids, id.data(), 20);
184
+ ids[20] = '\0';
185
+ char name_ch = '\0';
186
+ int v1 = 0, v2 = 0, v3 = 0;
187
+ if (std::sscanf(ids, "%c%3d-%3d-%3d--", &name_ch, &v1, &v2, &v3) == 4 && name_ch == 'M') {
188
+ return "Mainline " + std::to_string(v1) + "." + std::to_string(v2) + "." + std::to_string(v3);
189
+ }
190
+ }
191
+
192
+ bool first_12_zero = true;
193
+ for (int i = 0; i < 12; ++i) if (id[i] != 0) { first_12_zero = false; break; }
194
+ if (first_12_zero) {
195
+ if (id[12] == 0x97) return "Experimental 3.2.1b2";
196
+ if (id[12] == 0x00) return "Experimental 3.1";
197
+ return "Generic";
198
+ }
199
+
200
+ std::string unknown("Unknown [");
201
+ for (std::uint8_t c : id) unknown += (c >= 32 && c < 127) ? char(c) : '.';
202
+ unknown += "]";
203
+ return unknown;
204
+ }
205
+
206
+ } // namespace librats::bittorrent
@@ -0,0 +1,86 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file types.h
5
+ * @brief Core BitTorrent identifiers, constants and small identity helpers.
6
+ *
7
+ * Everything BitTorrent-specific lives in namespace librats::bittorrent. The
8
+ * 20-byte InfoHash is intentionally the *same* std::array type as the DHT's
9
+ * librats::InfoHash, so handing an info-hash to DhtClient::find_peers /
10
+ * announce_peer is a zero-conversion pass-through — no glue, no copying.
11
+ */
12
+
13
+ #include <array>
14
+ #include <cstddef>
15
+ #include <cstdint>
16
+ #include <optional>
17
+ #include <string>
18
+
19
+ namespace librats::bittorrent {
20
+
21
+ // ---- Sizes (bytes) ----
22
+ constexpr std::size_t kPeerIdSize = 20; ///< BEP 20 peer id length
23
+ constexpr std::size_t kInfoHashSize = 20; ///< SHA-1 of the bencoded info dictionary
24
+
25
+ /// 20-byte info hash. Same underlying type as librats::InfoHash (std::array<uint8_t,20>),
26
+ /// so it passes straight into DhtClient without conversion.
27
+ using InfoHash = std::array<std::uint8_t, kInfoHashSize>;
28
+
29
+ /// 20-byte peer identifier (BEP 20).
30
+ using PeerId = std::array<std::uint8_t, kPeerIdSize>;
31
+
32
+ /// The 8 reserved bytes exchanged in the BitTorrent handshake.
33
+ using ReservedBytes = std::array<std::uint8_t, 8>;
34
+
35
+ // ---- Protocol constants ----
36
+ constexpr char kProtocolString[] = "BitTorrent protocol"; // 19 chars (+NUL)
37
+ constexpr std::size_t kProtocolStringLen = 19;
38
+ constexpr std::size_t kHandshakeSize = 68; ///< 1 + 19 + 8 + 20 + 20
39
+ constexpr std::uint32_t kBlockSize = 16 * 1024; ///< standard request block (16 KiB)
40
+ constexpr std::uint32_t kMaxBlockSize = 32 * 1024; ///< largest block we will serve
41
+ constexpr std::uint32_t kDefaultPieceLength = 256 * 1024; ///< default for created torrents
42
+ constexpr std::uint32_t kMetadataPieceSize = 16 * 1024; ///< BEP 9 metadata block size
43
+ /// Largest ut_metadata (BEP 9) info dict we will accept from a peer. The size is
44
+ /// self-reported in the peer's extended handshake, so it must be capped before we
45
+ /// allocate a buffer for it — otherwise a hostile peer advertising ~4 GiB triggers
46
+ /// an out-of-memory allocation. libtorrent uses the same 4 MiB ceiling.
47
+ constexpr std::uint32_t kMaxMetadataSize = 4 * 1024 * 1024;
48
+
49
+ // ---- Reserved-bit negotiation (handshake) ----
50
+ // Each capability is a single bit in a specific reserved byte, per the relevant BEP.
51
+ namespace reserved {
52
+ inline void enable_dht(ReservedBytes& r) noexcept { r[7] |= 0x01; } // BEP 5 DHT
53
+ inline void enable_fast(ReservedBytes& r) noexcept { r[7] |= 0x04; } // BEP 6 Fast
54
+ inline void enable_extensions(ReservedBytes& r) noexcept { r[5] |= 0x10; } // BEP 10 Extension protocol
55
+
56
+ inline bool has_dht(const ReservedBytes& r) noexcept { return (r[7] & 0x01) != 0; }
57
+ inline bool has_fast(const ReservedBytes& r) noexcept { return (r[7] & 0x04) != 0; }
58
+ inline bool has_extensions(const ReservedBytes& r) noexcept { return (r[5] & 0x10) != 0; }
59
+ } // namespace reserved
60
+
61
+ // ---- Hex / identity helpers (defined in types.cpp) ----
62
+
63
+ /// Lowercase hex of an arbitrary byte run.
64
+ std::string to_hex(const std::uint8_t* data, std::size_t len);
65
+
66
+ /// Lowercase hex of any 20-byte id (InfoHash or PeerId — they share the type).
67
+ inline std::string to_hex(const std::array<std::uint8_t, 20>& id) {
68
+ return to_hex(id.data(), id.size());
69
+ }
70
+
71
+ /// Parse a 40-character hex string into an InfoHash. nullopt if the length is wrong
72
+ /// or a non-hex character is present.
73
+ std::optional<InfoHash> info_hash_from_hex(const std::string& hex);
74
+
75
+ /// True when every byte is zero (an unset / invalid hash).
76
+ bool is_all_zero(const std::array<std::uint8_t, 20>& id) noexcept;
77
+
78
+ /// Generate an Azureus-style peer id: a prefix (≤8 chars, e.g. "-LR0001-" for
79
+ /// librats v0.0.0.1) followed by a random tail filling the 20 bytes.
80
+ PeerId generate_peer_id(const std::string& client_prefix = "-LR0001-");
81
+
82
+ /// Best-effort human-readable client name + version from a peer id (BEP 20).
83
+ /// Returns "Unknown [...]" for unrecognised ids.
84
+ std::string identify_client(const PeerId& id);
85
+
86
+ } // namespace librats::bittorrent
@@ -0,0 +1,35 @@
1
+ #include "librats/core/address.h"
2
+ #include "librats/core/endpoint_parse.h"
3
+
4
+ #include <cassert>
5
+
6
+ namespace librats {
7
+
8
+ Address::Address(std::string_view numeric_ip, uint16_t port) : port(port) {
9
+ if (auto a = IpAddress::parse(numeric_ip)) {
10
+ ip = *a;
11
+ } else {
12
+ // A hostname or garbage reached the numeric-only Address constructor. In
13
+ // release builds we leave ip unspecified (is_valid() will report false);
14
+ // in debug we trip so the offending call site is caught early.
15
+ assert(false && "Address(string, port): not a numeric IP literal — use HostEndpoint");
16
+ }
17
+ }
18
+
19
+ std::optional<Address> Address::parse(std::string_view text) {
20
+ const auto hp = split_host_port(text);
21
+ if (!hp) return std::nullopt;
22
+ // Address is strictly numeric: a hostname host reaches here only as a parse
23
+ // failure, which is exactly the rejection we want (that input is a HostEndpoint).
24
+ auto addr = IpAddress::parse(hp->first);
25
+ if (!addr) return std::nullopt;
26
+ return Address{*addr, hp->second};
27
+ }
28
+
29
+ std::string Address::to_string() const {
30
+ std::string s = ip.to_string();
31
+ if (ip.is_v6()) return "[" + s + "]:" + std::to_string(port);
32
+ return s + ":" + std::to_string(port);
33
+ }
34
+
35
+ } // namespace librats
@@ -0,0 +1,78 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file address.h
5
+ * @brief A dialable transport endpoint: a numeric IP + port.
6
+ *
7
+ * The single ip+port endpoint type used throughout the library — by the node
8
+ * layer (dialing, peer info, reconnection) and by the lower-level engines (DHT,
9
+ * BitTorrent trackers, STUN/TURN). It lives in core/ because it is a foundational
10
+ * transport primitive that every layer above depends on.
11
+ *
12
+ * `ip` is an IpAddress: a strictly numeric, resolved address stored as raw bytes
13
+ * (see core/ip_address.h) — NOT a hostname and NOT a string. An unresolved
14
+ * "host:port" a user typed is a HostEndpoint (core/host_endpoint.h) and only
15
+ * becomes an Address once the resolver runs. This keeps Address a small,
16
+ * allocation-free, trivially-copyable value that hashes and compares by its bytes.
17
+ *
18
+ * IPv4 endpoints serialise as `host:port`; IPv6 endpoints serialise in bracketed
19
+ * `[ip]:port` form so the colons in the address are never confused with the port
20
+ * separator. parse() accepts both forms and is the exact inverse of to_string().
21
+ */
22
+
23
+ #include "librats/util/rats_export.h"
24
+ #include "librats/core/ip_address.h"
25
+
26
+ #include <cstdint>
27
+ #include <functional>
28
+ #include <optional>
29
+ #include <string>
30
+ #include <string_view>
31
+
32
+ namespace librats {
33
+
34
+ struct RATS_API Address {
35
+ IpAddress ip;
36
+ uint16_t port = 0;
37
+
38
+ Address() = default;
39
+ Address(IpAddress ip, uint16_t port) : ip(ip), port(port) {}
40
+
41
+ /// Build from a numeric IP literal + port. The literal MUST be a valid
42
+ /// IPv4/IPv6 address (hostnames belong in a HostEndpoint); an invalid literal
43
+ /// yields an unspecified ip (asserts in debug builds). Kept for the many call
44
+ /// sites of the form Address{"1.2.3.4", 80}.
45
+ Address(std::string_view numeric_ip, uint16_t port);
46
+
47
+ /// Parse "host:port" or "[ipv6]:port". Returns nullopt if the port is
48
+ /// missing/invalid, if the host is not a numeric IP literal, or for a bare
49
+ /// (unbracketed) IPv6 literal whose own colons make the port ambiguous.
50
+ /// Exact inverse of to_string().
51
+ static std::optional<Address> parse(std::string_view text);
52
+
53
+ /// IPv6 endpoints serialise bracketed; everything else plain. "" ip → ":port".
54
+ std::string to_string() const;
55
+
56
+ /// A usable dial target: a specified ip and a non-zero port.
57
+ bool is_valid() const noexcept { return !ip.is_unspecified() && port != 0; }
58
+
59
+ bool operator==(const Address& o) const noexcept { return port == o.port && ip == o.ip; }
60
+ bool operator!=(const Address& o) const noexcept { return !(*this == o); }
61
+ bool operator<(const Address& o) const noexcept {
62
+ return ip != o.ip ? ip < o.ip : port < o.port;
63
+ }
64
+ };
65
+
66
+ } // namespace librats
67
+
68
+ namespace std {
69
+ template <>
70
+ struct hash<librats::Address> {
71
+ std::size_t operator()(const librats::Address& a) const noexcept {
72
+ // Fold the port into the ip hash (FNV-style) — no temporary string.
73
+ std::size_t h = a.ip.hash();
74
+ h ^= (static_cast<std::size_t>(a.port) + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2));
75
+ return h;
76
+ }
77
+ };
78
+ } // namespace std
@@ -0,0 +1,69 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file bytes.h
5
+ * @brief Lightweight byte container aliases and a non-owning byte view.
6
+ *
7
+ * `ByteView` is a C++17 stand-in for std::span<const uint8_t>: a cheap
8
+ * (pointer, length) pair used to pass payloads around without copying. It does
9
+ * NOT own its storage — the referenced bytes must outlive the view. On the
10
+ * receive path views point straight into the connection's ReceiveBuffer and are
11
+ * only valid until the buffer is consumed.
12
+ *
13
+ * `ByteSpan` is the mutable counterpart (std::span<uint8_t>), handed out by
14
+ * ReceiveBuffer::prepare() as the destination of a recv().
15
+ */
16
+
17
+ #include <cstdint>
18
+ #include <cstddef>
19
+ #include <vector>
20
+ #include <string>
21
+
22
+ namespace librats {
23
+
24
+ using Bytes = std::vector<uint8_t>;
25
+
26
+ /// Non-owning view over a contiguous run of bytes.
27
+ class ByteView {
28
+ public:
29
+ constexpr ByteView() = default;
30
+ constexpr ByteView(const uint8_t* data, size_t size) : data_(data), size_(size) {}
31
+ ByteView(const Bytes& b) : data_(b.data()), size_(b.size()) {}
32
+ ByteView(const std::string& s)
33
+ : data_(reinterpret_cast<const uint8_t*>(s.data())), size_(s.size()) {}
34
+
35
+ const uint8_t* data() const noexcept { return data_; }
36
+ size_t size() const noexcept { return size_; }
37
+ bool empty() const noexcept { return size_ == 0; }
38
+
39
+ const uint8_t* begin() const noexcept { return data_; }
40
+ const uint8_t* end() const noexcept { return data_ + size_; }
41
+
42
+ Bytes to_bytes() const { return Bytes(data_, data_ + size_); }
43
+
44
+ private:
45
+ const uint8_t* data_ = nullptr;
46
+ size_t size_ = 0;
47
+ };
48
+
49
+ /// Non-owning view over a contiguous run of *writable* bytes.
50
+ class ByteSpan {
51
+ public:
52
+ constexpr ByteSpan() = default;
53
+ constexpr ByteSpan(uint8_t* data, size_t size) : data_(data), size_(size) {}
54
+
55
+ uint8_t* data() const noexcept { return data_; }
56
+ size_t size() const noexcept { return size_; }
57
+ bool empty() const noexcept { return size_ == 0; }
58
+
59
+ uint8_t* begin() const noexcept { return data_; }
60
+ uint8_t* end() const noexcept { return data_ + size_; }
61
+
62
+ operator ByteView() const noexcept { return ByteView(data_, size_); }
63
+
64
+ private:
65
+ uint8_t* data_ = nullptr;
66
+ size_t size_ = 0;
67
+ };
68
+
69
+ } // namespace librats
@@ -0,0 +1,172 @@
1
+ #include "librats/core/chained_send_buffer.h"
2
+
3
+ #include <algorithm>
4
+ #include <cassert>
5
+
6
+ namespace librats {
7
+
8
+ ChainedSendBuffer& ChainedSendBuffer::operator=(ChainedSendBuffer&& other) noexcept {
9
+ if (this == &other) return *this;
10
+
11
+ // clear() the source rather than relying on the containers' moved-from state: a
12
+ // moved-from vector is only guaranteed *valid*, not empty, and the counters must
13
+ // be zeroed to match whatever it ends up holding.
14
+ chunks_ = std::move(other.chunks_);
15
+ recycled_ = std::move(other.recycled_);
16
+ head_ = std::exchange(other.head_, 0);
17
+ pending_ = std::exchange(other.pending_, 0);
18
+ allocated_ = std::exchange(other.allocated_, 0);
19
+ other.clear();
20
+ return *this;
21
+ }
22
+
23
+ // ── Queueing ────────────────────────────────────────────────────────────────
24
+
25
+ void ChainedSendBuffer::append(Bytes data) {
26
+ if (data.empty()) return;
27
+
28
+ pending_ += data.size();
29
+ allocated_ += data.capacity();
30
+ chunks_.push_back(Chunk{std::move(data), 0});
31
+ }
32
+
33
+ void ChainedSendBuffer::append(ByteView bytes) {
34
+ if (bytes.empty()) return;
35
+
36
+ // Pack into the tail chunk when it has the room. The capacity check guarantees
37
+ // the insert cannot reallocate, so slices handed out by gather() — including one
38
+ // pointing into this very chunk — stay valid.
39
+ if (Bytes* tail = coalescable_tail(bytes.size())) {
40
+ tail->insert(tail->end(), bytes.begin(), bytes.end());
41
+ pending_ += bytes.size();
42
+ return;
43
+ }
44
+
45
+ Bytes chunk = take_chunk(bytes.size());
46
+ chunk.assign(bytes.begin(), bytes.end());
47
+ append(std::move(chunk));
48
+ }
49
+
50
+ Bytes* ChainedSendBuffer::coalescable_tail(size_t bytes) noexcept {
51
+ if (head_ == chunks_.size()) return nullptr; // no live chunk to pack into
52
+ Bytes& tail = chunks_.back().data;
53
+ return tail.capacity() - tail.size() >= bytes ? &tail : nullptr;
54
+ }
55
+
56
+ Bytes ChainedSendBuffer::take_chunk(size_t bytes) {
57
+ if (recycled_.capacity() >= bytes) {
58
+ Bytes chunk = std::move(recycled_);
59
+ recycled_ = Bytes{}; // a moved-from vector is only guaranteed *valid*, not empty
60
+ return chunk;
61
+ }
62
+
63
+ // A small message opens a chunk with spare room, so the messages behind it can
64
+ // be packed in for free (see coalescable_tail). A large one is sized exactly —
65
+ // padding a payload buffer would only waste memory.
66
+ Bytes chunk;
67
+ chunk.reserve((std::max)(bytes, kScratchCapacity));
68
+ return chunk;
69
+ }
70
+
71
+ void ChainedSendBuffer::recycle(Bytes&& chunk) noexcept {
72
+ // Take ownership unconditionally, so a chunk we decline is freed *here* rather
73
+ // than lingering in its (already dead, but not yet swept) slot. pop_front() has
74
+ // just subtracted this chunk's capacity from allocated_, and allocated() is what
75
+ // the send high-water mark watches — leaving the buffer alive until
76
+ // reclaim_drained_slots() gets round to it would let the queue hold up to twice
77
+ // the mark while reporting that it is under it.
78
+ Bytes dead = std::move(chunk);
79
+
80
+ // Keep at most one drained chunk, and only a small one: holding on to a piece-
81
+ // sized payload buffer would trade a malloc for permanently resident memory.
82
+ if (dead.capacity() > kMaxRecycledCapacity) return;
83
+ if (dead.capacity() <= recycled_.capacity()) return;
84
+
85
+ recycled_ = std::move(dead);
86
+ recycled_.clear(); // keeps the capacity
87
+ }
88
+
89
+ // ── Draining ────────────────────────────────────────────────────────────────
90
+
91
+ size_t ChainedSendBuffer::gather(ByteView* out, size_t max_slices, size_t max_bytes) const {
92
+ size_t n = 0;
93
+ size_t bytes = 0;
94
+ for (size_t i = head_; i < chunks_.size() && n < max_slices; ++i) {
95
+ const Chunk& chunk = chunks_[i];
96
+ out[n++] = ByteView(chunk.head(), chunk.remaining());
97
+
98
+ // Checked *after* taking the slice, so the first one is always described even
99
+ // if it alone exceeds the budget — a chunk larger than max_bytes must still be
100
+ // sendable, and a short iovec never loses data: whatever the kernel does not
101
+ // take this round goes out on the next.
102
+ bytes += chunk.remaining();
103
+ if (bytes >= max_bytes) break;
104
+ }
105
+ return n;
106
+ }
107
+
108
+ ByteView ChainedSendBuffer::front() const noexcept {
109
+ if (head_ == chunks_.size()) return {};
110
+ const Chunk& head = chunks_[head_];
111
+ return ByteView(head.head(), head.remaining());
112
+ }
113
+
114
+ void ChainedSendBuffer::pop_front(size_t bytes) {
115
+ assert(bytes <= pending_ && "pop_front() past the queued bytes");
116
+
117
+ while (bytes > 0 && head_ < chunks_.size()) {
118
+ Chunk& head = chunks_[head_];
119
+ const size_t left = head.remaining();
120
+
121
+ if (bytes < left) { // chunk partially sent: just advance its cursor
122
+ head.sent += bytes;
123
+ pending_ -= bytes;
124
+ break; // NOT return — the drained slots behind us still need sweeping
125
+ }
126
+
127
+ bytes -= left;
128
+ pending_ -= left;
129
+ allocated_ -= head.data.capacity();
130
+ recycle(std::move(head.data));
131
+ ++head_; // the slot stays; reclaim_drained_slots() sweeps it up in bulk
132
+ }
133
+ reclaim_drained_slots();
134
+ }
135
+
136
+ void ChainedSendBuffer::reclaim_drained_slots() {
137
+ if (head_ == 0) return;
138
+
139
+ if (head_ == chunks_.size()) {
140
+ // Fully drained — the common case. Rewinding to 0 keeps the vector's storage
141
+ // for the next burst, so a steady drip of messages allocates nothing at all:
142
+ // neither a payload buffer (recycled_) nor a slot to hang it off.
143
+ chunks_.clear();
144
+ head_ = 0;
145
+ if (chunks_.capacity() > kMaxRetainedSlots) {
146
+ chunks_.shrink_to_fit(); // hand back the array a backlog grew
147
+ chunks_.reserve(kMaxRetainedSlots); // …but not the steady-state working set
148
+ }
149
+ return;
150
+ }
151
+
152
+ // A backlog that never fully empties: drop the dead prefix once it is at least
153
+ // half the vector, so the moves are paid for by the slots they retire (amortised
154
+ // O(1) per chunk) rather than run on every pop.
155
+ if (head_ * 2 >= chunks_.size()) {
156
+ chunks_.erase(chunks_.begin(), chunks_.begin() + static_cast<std::ptrdiff_t>(head_));
157
+ head_ = 0;
158
+ }
159
+ }
160
+
161
+ void ChainedSendBuffer::clear() noexcept {
162
+ // swap-with-empty rather than clear()+shrink_to_fit(): constructing an empty
163
+ // vector cannot allocate, so this releases the storage without the throwing
164
+ // reallocation shrink_to_fit() is allowed to perform (this function is noexcept).
165
+ std::vector<Chunk>().swap(chunks_);
166
+ Bytes().swap(recycled_);
167
+ head_ = 0;
168
+ pending_ = 0;
169
+ allocated_ = 0;
170
+ }
171
+
172
+ } // namespace librats