librats 1.0.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/README.md +145 -331
  2. package/binding.gyp +16 -3
  3. package/lib/index.d.ts +288 -696
  4. package/lib/index.js +407 -44
  5. package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
  7. package/native-src/CMakeLists.txt +404 -179
  8. package/native-src/LICENSE +1 -1
  9. package/native-src/src/librats/bindings/rats.cpp +762 -0
  10. package/native-src/src/librats/bindings/rats.h +380 -0
  11. package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
  12. package/native-src/src/librats/bittorrent/bencode.h +176 -0
  13. package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
  14. package/native-src/src/librats/bittorrent/bitfield.h +76 -0
  15. package/native-src/src/librats/bittorrent/byte_io.h +58 -0
  16. package/native-src/src/librats/bittorrent/choker.cpp +25 -0
  17. package/native-src/src/librats/bittorrent/choker.h +46 -0
  18. package/native-src/src/librats/bittorrent/client.cpp +413 -0
  19. package/native-src/src/librats/bittorrent/client.h +227 -0
  20. package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
  21. package/native-src/src/librats/bittorrent/disk_io.h +150 -0
  22. package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
  23. package/native-src/src/librats/bittorrent/extensions.h +94 -0
  24. package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
  25. package/native-src/src/librats/bittorrent/file_storage.h +79 -0
  26. package/native-src/src/librats/bittorrent/log.h +42 -0
  27. package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
  28. package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
  29. package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
  30. package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
  31. package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
  32. package/native-src/src/librats/bittorrent/peer_list.h +75 -0
  33. package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
  34. package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
  35. package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
  36. package/native-src/src/librats/bittorrent/reactor.h +89 -0
  37. package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
  38. package/native-src/src/librats/bittorrent/resume_data.h +41 -0
  39. package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
  40. package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
  41. package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
  42. package/native-src/src/librats/bittorrent/torrent.h +260 -0
  43. package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
  44. package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
  45. package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
  46. package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
  47. package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
  48. package/native-src/src/librats/bittorrent/tracker.h +108 -0
  49. package/native-src/src/librats/bittorrent/types.cpp +206 -0
  50. package/native-src/src/librats/bittorrent/types.h +86 -0
  51. package/native-src/src/librats/core/address.cpp +35 -0
  52. package/native-src/src/librats/core/address.h +78 -0
  53. package/native-src/src/librats/core/bytes.h +69 -0
  54. package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
  55. package/native-src/src/librats/core/chained_send_buffer.h +183 -0
  56. package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
  57. package/native-src/src/librats/core/endpoint_parse.h +31 -0
  58. package/native-src/src/librats/core/event_bus.h +70 -0
  59. package/native-src/src/librats/core/host_endpoint.h +56 -0
  60. package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
  61. package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
  62. package/native-src/src/librats/core/ip_address.cpp +120 -0
  63. package/native-src/src/librats/core/ip_address.h +109 -0
  64. package/native-src/src/librats/core/mpsc_queue.h +47 -0
  65. package/native-src/src/librats/core/notifier.h +74 -0
  66. package/native-src/src/librats/core/receive_buffer.cpp +219 -0
  67. package/native-src/src/librats/core/receive_buffer.h +171 -0
  68. package/native-src/src/librats/core/service_registry.h +58 -0
  69. package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
  70. package/native-src/src/librats/core/socket.h +496 -0
  71. package/native-src/src/librats/core/timer_queue.h +105 -0
  72. package/native-src/src/librats/core/types.cpp +43 -0
  73. package/native-src/src/librats/core/types.h +103 -0
  74. package/native-src/src/librats/core/wakeup_pipe.h +83 -0
  75. package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
  76. package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
  77. package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
  78. package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
  79. package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
  80. package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
  81. package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
  82. package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
  83. package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
  84. package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
  85. package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
  86. package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
  87. package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
  88. package/native-src/src/librats/crypto/hkdf.c +266 -0
  89. package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
  90. package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
  91. package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
  92. package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
  93. package/native-src/src/librats/crypto/poly1305.h +37 -0
  94. package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
  95. package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
  96. package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
  97. package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
  98. package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
  99. package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
  100. package/native-src/src/librats/dht/announce.cpp +37 -0
  101. package/native-src/src/librats/dht/announce.h +41 -0
  102. package/native-src/src/librats/dht/bep42.cpp +109 -0
  103. package/native-src/src/librats/dht/bep42.h +48 -0
  104. package/native-src/src/librats/dht/dht.cpp +501 -0
  105. package/native-src/src/librats/dht/dht.h +119 -0
  106. package/native-src/src/librats/dht/dht_runner.cpp +103 -0
  107. package/native-src/src/librats/dht/dht_runner.h +71 -0
  108. package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
  109. package/native-src/src/librats/dht/dos_blocker.h +47 -0
  110. package/native-src/src/librats/dht/find_peers.cpp +52 -0
  111. package/native-src/src/librats/dht/find_peers.h +73 -0
  112. package/native-src/src/librats/dht/id.h +167 -0
  113. package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
  114. package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
  115. package/native-src/src/librats/dht/log.h +38 -0
  116. package/native-src/src/librats/dht/node.cpp +473 -0
  117. package/native-src/src/librats/dht/node.h +164 -0
  118. package/native-src/src/librats/dht/node_entry.h +81 -0
  119. package/native-src/src/librats/dht/observer.h +72 -0
  120. package/native-src/src/librats/dht/persistence.cpp +90 -0
  121. package/native-src/src/librats/dht/persistence.h +32 -0
  122. package/native-src/src/librats/dht/routing_table.cpp +559 -0
  123. package/native-src/src/librats/dht/routing_table.h +185 -0
  124. package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
  125. package/native-src/src/librats/dht/rpc_manager.h +77 -0
  126. package/native-src/src/librats/dht/storage.cpp +92 -0
  127. package/native-src/src/librats/dht/storage.h +74 -0
  128. package/native-src/src/librats/dht/transport.h +27 -0
  129. package/native-src/src/librats/dht/traversal.cpp +326 -0
  130. package/native-src/src/librats/dht/traversal.h +120 -0
  131. package/native-src/src/librats/dht/udp_transport.cpp +49 -0
  132. package/native-src/src/librats/dht/udp_transport.h +51 -0
  133. package/native-src/src/librats/mdns/log.h +22 -0
  134. package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
  135. package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
  136. package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
  137. package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
  138. package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
  139. package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
  140. package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
  141. package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
  142. package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
  143. package/native-src/src/librats/node/circuit_service.h +84 -0
  144. package/native-src/src/librats/node/config.h +110 -0
  145. package/native-src/src/librats/node/dial_service.h +54 -0
  146. package/native-src/src/librats/node/dialer.cpp +264 -0
  147. package/native-src/src/librats/node/dialer.h +188 -0
  148. package/native-src/src/librats/node/host_events.h +26 -0
  149. package/native-src/src/librats/node/identify.cpp +130 -0
  150. package/native-src/src/librats/node/identify.h +71 -0
  151. package/native-src/src/librats/node/nat_status.cpp +103 -0
  152. package/native-src/src/librats/node/nat_status.h +118 -0
  153. package/native-src/src/librats/node/node.cpp +865 -0
  154. package/native-src/src/librats/node/node.h +344 -0
  155. package/native-src/src/librats/node/node_context.h +33 -0
  156. package/native-src/src/librats/node/peer_network.h +91 -0
  157. package/native-src/src/librats/peer/peer.h +49 -0
  158. package/native-src/src/librats/peer/peer_book.cpp +181 -0
  159. package/native-src/src/librats/peer/peer_book.h +88 -0
  160. package/native-src/src/librats/peer/peer_id.cpp +72 -0
  161. package/native-src/src/librats/peer/peer_id.h +62 -0
  162. package/native-src/src/librats/peer/peer_info.h +37 -0
  163. package/native-src/src/librats/peer/peer_table.cpp +170 -0
  164. package/native-src/src/librats/peer/peer_table.h +148 -0
  165. package/native-src/src/librats/security/handshaker.h +66 -0
  166. package/native-src/src/librats/security/identity.h +43 -0
  167. package/native-src/src/librats/security/noise_security.cpp +122 -0
  168. package/native-src/src/librats/security/noise_security.h +37 -0
  169. package/native-src/src/librats/security/plaintext_security.h +106 -0
  170. package/native-src/src/librats/security/session.h +37 -0
  171. package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
  172. package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
  173. package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
  174. package/native-src/src/librats/subsystems/bittorrent.h +136 -0
  175. package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
  176. package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
  177. package/native-src/src/librats/subsystems/dht_service.h +36 -0
  178. package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
  179. package/native-src/src/librats/subsystems/file_transfer.h +367 -0
  180. package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
  181. package/native-src/src/librats/subsystems/hole_punch.h +290 -0
  182. package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
  183. package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
  184. package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
  185. package/native-src/src/librats/subsystems/message_json.cpp +112 -0
  186. package/native-src/src/librats/subsystems/message_json.h +88 -0
  187. package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
  188. package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
  189. package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
  190. package/native-src/src/librats/subsystems/ping_service.h +66 -0
  191. package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
  192. package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
  193. package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
  194. package/native-src/src/librats/subsystems/pubsub.h +175 -0
  195. package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
  196. package/native-src/src/librats/subsystems/reconnection.h +126 -0
  197. package/native-src/src/librats/subsystems/relay.cpp +1142 -0
  198. package/native-src/src/librats/subsystems/relay.h +211 -0
  199. package/native-src/src/librats/subsystems/relay_service.h +46 -0
  200. package/native-src/src/librats/transport/connection.cpp +343 -0
  201. package/native-src/src/librats/transport/connection.h +283 -0
  202. package/native-src/src/librats/transport/link.h +96 -0
  203. package/native-src/src/librats/transport/reactor.cpp +588 -0
  204. package/native-src/src/librats/transport/reactor.h +262 -0
  205. package/native-src/src/librats/transport/reactor_pool.h +81 -0
  206. package/native-src/src/librats/transport/relay_link.cpp +208 -0
  207. package/native-src/src/librats/transport/relay_link.h +303 -0
  208. package/native-src/src/librats/transport/tcp_link.cpp +49 -0
  209. package/native-src/src/librats/transport/tcp_link.h +43 -0
  210. package/native-src/src/librats/transport/udp_mux.cpp +617 -0
  211. package/native-src/src/librats/transport/udp_mux.h +363 -0
  212. package/native-src/src/librats/transport/udp_packet.cpp +121 -0
  213. package/native-src/src/librats/transport/udp_packet.h +190 -0
  214. package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
  215. package/native-src/src/librats/transport/udp_stream.h +614 -0
  216. package/native-src/src/librats/util/features.h.in +51 -0
  217. package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
  218. package/native-src/src/librats/util/fs.h +136 -0
  219. package/native-src/src/librats/util/json.cpp +1002 -0
  220. package/native-src/src/librats/util/json.h +444 -0
  221. package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
  222. package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
  223. package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
  224. package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
  225. package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
  226. package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
  227. package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
  228. package/native-src/src/librats/util/rats_export.h +69 -0
  229. package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
  230. package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
  231. package/native-src/src/librats/wire/frame.cpp +76 -0
  232. package/native-src/src/librats/wire/frame.h +111 -0
  233. package/native-src/src/librats/wire/message_router.cpp +45 -0
  234. package/native-src/src/librats/wire/message_router.h +47 -0
  235. package/package.json +5 -4
  236. package/scripts/build-librats.js +1 -0
  237. package/scripts/postinstall.js +3 -3
  238. package/scripts/prepare-package.js +4 -4
  239. package/scripts/verify-installation.js +63 -105
  240. package/src/librats_node.cpp +1067 -1323
  241. package/native-src/src/bencode.cpp +0 -485
  242. package/native-src/src/bencode.h +0 -145
  243. package/native-src/src/bittorrent.cpp +0 -14
  244. package/native-src/src/bittorrent.h +0 -74
  245. package/native-src/src/bt_bitfield.cpp +0 -372
  246. package/native-src/src/bt_bitfield.h +0 -316
  247. package/native-src/src/bt_choker.cpp +0 -228
  248. package/native-src/src/bt_choker.h +0 -147
  249. package/native-src/src/bt_client.cpp +0 -1047
  250. package/native-src/src/bt_client.h +0 -445
  251. package/native-src/src/bt_create_torrent.cpp +0 -677
  252. package/native-src/src/bt_create_torrent.h +0 -473
  253. package/native-src/src/bt_extension.cpp +0 -469
  254. package/native-src/src/bt_extension.h +0 -309
  255. package/native-src/src/bt_file_storage.cpp +0 -261
  256. package/native-src/src/bt_file_storage.h +0 -298
  257. package/native-src/src/bt_handshake.cpp +0 -134
  258. package/native-src/src/bt_handshake.h +0 -157
  259. package/native-src/src/bt_messages.cpp +0 -364
  260. package/native-src/src/bt_messages.h +0 -324
  261. package/native-src/src/bt_network.cpp +0 -1007
  262. package/native-src/src/bt_network.h +0 -417
  263. package/native-src/src/bt_peer_connection.cpp +0 -742
  264. package/native-src/src/bt_peer_connection.h +0 -592
  265. package/native-src/src/bt_piece_picker.cpp +0 -786
  266. package/native-src/src/bt_piece_picker.h +0 -473
  267. package/native-src/src/bt_resume_data.cpp +0 -410
  268. package/native-src/src/bt_resume_data.h +0 -249
  269. package/native-src/src/bt_torrent.cpp +0 -2120
  270. package/native-src/src/bt_torrent.h +0 -641
  271. package/native-src/src/bt_torrent_info.cpp +0 -659
  272. package/native-src/src/bt_torrent_info.h +0 -418
  273. package/native-src/src/bt_types.h +0 -621
  274. package/native-src/src/chained_send_buffer.cpp +0 -75
  275. package/native-src/src/chained_send_buffer.h +0 -137
  276. package/native-src/src/crypto/hkdf.c +0 -266
  277. package/native-src/src/crypto/poly1305.h +0 -36
  278. package/native-src/src/dht.cpp +0 -3311
  279. package/native-src/src/dht.h +0 -717
  280. package/native-src/src/disk_io.cpp +0 -632
  281. package/native-src/src/disk_io.h +0 -315
  282. package/native-src/src/file_transfer.cpp +0 -1415
  283. package/native-src/src/file_transfer.h +0 -286
  284. package/native-src/src/fs.h +0 -108
  285. package/native-src/src/gossipsub.cpp +0 -1139
  286. package/native-src/src/gossipsub.h +0 -403
  287. package/native-src/src/ice.cpp +0 -893
  288. package/native-src/src/ice.h +0 -559
  289. package/native-src/src/json.hpp +0 -25526
  290. package/native-src/src/librats.cpp +0 -2378
  291. package/native-src/src/librats.h +0 -2324
  292. package/native-src/src/librats_bittorrent.cpp +0 -601
  293. package/native-src/src/librats_c.cpp +0 -1557
  294. package/native-src/src/librats_c.h +0 -323
  295. package/native-src/src/librats_discovery.cpp +0 -402
  296. package/native-src/src/librats_encryption.cpp +0 -275
  297. package/native-src/src/librats_file_transfer.cpp +0 -144
  298. package/native-src/src/librats_gossipsub.cpp +0 -289
  299. package/native-src/src/librats_ice.cpp +0 -213
  300. package/native-src/src/librats_log_macros.h +0 -36
  301. package/native-src/src/librats_logging.cpp +0 -173
  302. package/native-src/src/librats_mdns.cpp +0 -166
  303. package/native-src/src/librats_persistence.cpp +0 -796
  304. package/native-src/src/librats_portmap.cpp +0 -419
  305. package/native-src/src/librats_reconnection.cpp +0 -218
  306. package/native-src/src/librats_statistic.cpp +0 -105
  307. package/native-src/src/librats_storage.cpp +0 -189
  308. package/native-src/src/rats_export.h +0 -17
  309. package/native-src/src/receive_buffer.cpp +0 -82
  310. package/native-src/src/receive_buffer.h +0 -127
  311. package/native-src/src/socket.h +0 -228
  312. package/native-src/src/threadmanager.cpp +0 -105
  313. package/native-src/src/threadmanager.h +0 -53
  314. package/native-src/src/tracker.cpp +0 -1264
  315. package/native-src/src/tracker.h +0 -319
  316. package/native-src/src/turn.cpp +0 -762
  317. package/native-src/src/turn.h +0 -460
  318. package/native-src/src/wakeup_pipe.h +0 -60
  319. /package/native-src/src/{os.h → librats/util/os.h} +0 -0
@@ -1,2378 +0,0 @@
1
- #include "librats.h"
2
- #include "os.h"
3
- #include "network_utils.h"
4
- #include "network_monitor.h" // complete type for unique_ptr<NetworkMonitor> member
5
- #include "version.h"
6
- #include <algorithm>
7
- #include <array>
8
- #include <random>
9
- #include <sstream>
10
- #include <iomanip>
11
- #include <string_view>
12
-
13
- #include "librats_log_macros.h"
14
-
15
- namespace librats {
16
-
17
- // Configuration file constants
18
- const std::string RatsClient::CONFIG_FILE_NAME = "config.json";
19
- const std::string RatsClient::PEERS_FILE_NAME = "peers.rats";
20
- const std::string RatsClient::PEERS_EVER_FILE_NAME = "peers_ever.rats";
21
-
22
- // =========================================================================
23
- // Constructor and Destructor
24
- // =========================================================================
25
-
26
- RatsClient::RatsClient(int listen_port, int max_peers, const std::string& bind_address)
27
- : listen_port_(listen_port),
28
- bind_address_(bind_address),
29
- max_peers_(max_peers),
30
- server_socket_(INVALID_SOCKET_VALUE),
31
- running_(false),
32
- // [1] Configuration persistence
33
- data_directory_("."),
34
- // [2] Custom protocol configuration
35
- custom_protocol_name_("rats"),
36
- custom_protocol_version_("1.0"),
37
- // [3] Encryption state
38
- encryption_enabled_(false),
39
- noise_keypair_initialized_(false),
40
- // Automatic discovery
41
- auto_discovery_running_(false) {
42
- // Load configuration (this will generate peer ID if needed)
43
- load_configuration();
44
-
45
- // Initialize modules
46
- initialize_modules();
47
- }
48
-
49
- RatsClient::~RatsClient() {
50
- stop();
51
- // Destroy modules
52
- destroy_modules();
53
- }
54
-
55
- // =========================================================================
56
- // Modules Initialization and Destruction
57
- // =========================================================================
58
-
59
- void RatsClient::initialize_modules() {
60
- // Initialize GossipSub
61
- if (!gossipsub_) {
62
- LOG_CLIENT_INFO("Initializing GossipSub");
63
- gossipsub_ = std::make_unique<GossipSub>(*this);
64
- }
65
-
66
- // Initialize File Transfer Manager
67
- if (!file_transfer_manager_) {
68
- LOG_CLIENT_INFO("Initializing File Transfer Manager");
69
- file_transfer_manager_ = std::make_unique<FileTransferManager>(*this);
70
- }
71
- }
72
-
73
- void RatsClient::destroy_modules() {
74
- if (gossipsub_) {
75
- LOG_CLIENT_INFO("Destroying GossipSub");
76
- gossipsub_.reset();
77
- }
78
-
79
- if (file_transfer_manager_) {
80
- LOG_CLIENT_INFO("Destroying File Transfer Manager");
81
- file_transfer_manager_.reset();
82
- }
83
- }
84
-
85
- // =========================================================================
86
- // Core Lifecycle Management
87
- // =========================================================================
88
-
89
- bool RatsClient::start() {
90
- if (running_.load()) {
91
- LOG_CLIENT_WARN("RatsClient is already running");
92
- return false;
93
- }
94
-
95
- LOG_CLIENT_INFO("Starting RatsClient on port " << listen_port_ <<
96
- (bind_address_.empty() ? "" : " bound to " + bind_address_));
97
-
98
- // Print system information for debugging and log analysis
99
- SystemInfo sys_info = get_system_info();
100
- LOG_CLIENT_INFO("=== System Information ===");
101
- LOG_CLIENT_INFO("OS: " << sys_info.os_name << " " << sys_info.os_version);
102
- LOG_CLIENT_INFO("Architecture: " << sys_info.architecture);
103
- LOG_CLIENT_INFO("Hostname: " << sys_info.hostname);
104
- LOG_CLIENT_INFO("CPU: " << sys_info.cpu_model);
105
- LOG_CLIENT_INFO("CPU Cores: " << sys_info.cpu_cores << " physical, " << sys_info.cpu_logical_cores << " logical");
106
- LOG_CLIENT_INFO("Memory: " << sys_info.total_memory_mb << " MB total, " << sys_info.available_memory_mb << " MB available");
107
- LOG_CLIENT_INFO("===========================");
108
-
109
- // Initialize socket library first (required for all socket operations)
110
- init_socket_library();
111
-
112
- // Initialize encryption
113
- if (!initialize_encryption(encryption_enabled_)) {
114
- LOG_CLIENT_ERROR("Failed to initialize encryption");
115
- return false;
116
- }
117
-
118
- // Initialize local interface addresses for connection blocking
119
- initialize_local_addresses();
120
-
121
- // Create dual-stack server socket (supports both IPv4 and IPv6)
122
- server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
123
- // Fallback to free port
124
- if (!is_valid_socket(server_socket_) && listen_port_ > 0) {
125
- // Requested port is not available, try ephemeral port as fallback
126
- int original_port = listen_port_;
127
- LOG_CLIENT_WARN("TCP port " << original_port << " is not available, falling back to ephemeral port");
128
- server_socket_ = create_tcp_server(0, 5, bind_address_);
129
- if (is_valid_socket(server_socket_)) {
130
- listen_port_ = get_bound_port(server_socket_);
131
- LOG_CLIENT_INFO("Fell back from port " << original_port << " to ephemeral port " << listen_port_);
132
- }
133
- }
134
- if (!is_valid_socket(server_socket_)) {
135
- LOG_CLIENT_ERROR("Failed to create server socket on port " << listen_port_ <<
136
- (bind_address_.empty() ? "" : " bound to " + bind_address_));
137
- return false;
138
- }
139
-
140
- // Update listen_port_ with actual bound port if ephemeral port was requested
141
- if (listen_port_ == 0) {
142
- listen_port_ = get_bound_port(server_socket_);
143
- if (listen_port_ == 0) {
144
- LOG_CLIENT_WARN("Failed to get actual bound port - using port 0");
145
- } else {
146
- LOG_CLIENT_INFO("Server bound to ephemeral port " << listen_port_);
147
- }
148
- }
149
-
150
- // Set server socket to non-blocking for the IO poller
151
- set_socket_nonblocking(server_socket_);
152
-
153
- // Create platform-optimal IO poller and register server socket
154
- poller_ = IOPoller::create();
155
- poller_->add(server_socket_, PollIn);
156
- LOG_CLIENT_INFO("IO poller backend: " << poller_->name());
157
-
158
- running_.store(true);
159
-
160
- // Start IO thread (single-threaded event loop for all sockets)
161
- io_thread_ = std::thread(&RatsClient::io_loop, this);
162
-
163
- // Start management thread (handshake timeouts, reconnection, thread cleanup)
164
- management_thread_ = std::thread(&RatsClient::management_loop, this);
165
-
166
- // Start GossipSub
167
- if (gossipsub_ && !gossipsub_->start()) {
168
- LOG_CLIENT_WARN("Failed to start GossipSub - continuing without it");
169
- }
170
-
171
- // Start automatic port forwarding (UPnP/NAT-PMP) for the bound listen port.
172
- // No-op when disabled; runs discovery/mapping on its own background threads.
173
- start_port_mapping();
174
-
175
- // React to host network changes (IP/interface/route): renew port mappings,
176
- // re-discover the public address and re-announce. No-op when disabled.
177
- start_network_monitor();
178
-
179
- LOG_CLIENT_INFO("RatsClient started successfully on port " << listen_port_);
180
-
181
- // Attempt to reconnect to saved peers
182
- add_managed_thread(std::thread([this]() {
183
- // Give the server some time to fully initialize
184
- std::this_thread::sleep_for(std::chrono::milliseconds(PEER_RECONNECT_DELAY_MS));
185
- int reconnect_attempts = load_and_reconnect_peers();
186
- if (reconnect_attempts > 0) {
187
- LOG_CLIENT_INFO("Attempted to reconnect to " << reconnect_attempts << " saved peers");
188
- }
189
-
190
- // Also attempt to reconnect to historical peers if not at peer limit
191
- if (!is_peer_limit_reached()) {
192
- std::this_thread::sleep_for(std::chrono::milliseconds(HISTORICAL_RECONNECT_DELAY_MS));
193
- int historical_attempts = load_and_reconnect_historical_peers();
194
- if (historical_attempts > 0) {
195
- LOG_CLIENT_INFO("Attempted to reconnect to " << historical_attempts << " historical peers");
196
- }
197
- }
198
- }), "peer-reconnection");
199
-
200
- return true;
201
- }
202
-
203
- void RatsClient::stop() {
204
- if (!running_.load()) {
205
- return;
206
- }
207
-
208
- LOG_CLIENT_INFO("Stopping RatsClient");
209
-
210
- // Stop network-change detection FIRST and join its recovery worker: that
211
- // worker touches the port-mapping backends and the DHT clients, both torn
212
- // down below, so it must not be running past this point.
213
- stop_network_monitor();
214
-
215
- // Remove port mappings and stop UPnP/NAT-PMP backends before tearing down
216
- // sockets (best-effort cleanup so we don't leave stale router mappings).
217
- stop_port_mapping();
218
-
219
- // Stop GossipSub (can broadcast stop message)
220
- if (gossipsub_) {
221
- gossipsub_->stop();
222
- }
223
-
224
- // Trigger immediate shutdown of all background threads
225
- shutdown_all_threads();
226
-
227
- // Stop DHT discovery (this will also stop automatic discovery)
228
- stop_dht_discovery();
229
-
230
- // Stop mDNS discovery
231
- stop_mdns_discovery();
232
-
233
- // Close server socket to break accept loop
234
- if (is_valid_socket(server_socket_)) {
235
- close_socket(server_socket_, true);
236
- server_socket_ = INVALID_SOCKET_VALUE;
237
- }
238
-
239
- // Clear reconnection queue to prevent reconnection attempts during shutdown
240
- {
241
- std::lock_guard<std::mutex> lock(reconnect_mutex_);
242
- reconnect_queue_.clear();
243
- manual_disconnect_peers_.clear();
244
- }
245
-
246
- // Close all peer connections and remove from poller
247
- {
248
- std::lock_guard<std::mutex> lock(peers_mutex_);
249
- LOG_CLIENT_INFO("Closing " << peers_.size() << " peer connections");
250
- for (const auto& pair : peers_) {
251
- const RatsPeer& peer = pair.second;
252
- if (poller_) poller_->remove(peer.socket);
253
- close_socket(peer.socket, true);
254
- }
255
- peers_.clear();
256
- socket_to_peer_id_.clear();
257
- address_to_peer_id_.clear();
258
- validated_peer_count_.store(0, std::memory_order_relaxed);
259
- }
260
-
261
- // Wait for IO thread to finish
262
- if (io_thread_.joinable()) {
263
- LOG_CLIENT_DEBUG("Waiting for IO thread to finish");
264
- io_thread_.join();
265
- }
266
-
267
- // Wait for management thread to finish
268
- if (management_thread_.joinable()) {
269
- LOG_CLIENT_DEBUG("Waiting for management thread to finish");
270
- management_thread_.join();
271
- }
272
-
273
- // Destroy poller after threads have stopped
274
- poller_.reset();
275
-
276
- // Join all managed threads for graceful cleanup
277
- join_all_active_threads();
278
-
279
- cleanup_socket_library();
280
-
281
- // Save configuration before stopping
282
- save_configuration();
283
-
284
- LOG_CLIENT_INFO("RatsClient stopped successfully");
285
- }
286
-
287
- void RatsClient::shutdown_all_threads() {
288
- LOG_CLIENT_INFO("Initiating shutdown of all background threads");
289
-
290
- // Signal all threads to stop
291
- running_.store(false);
292
-
293
- // Call parent class to handle thread management shutdown
294
- ThreadManager::shutdown_all_threads();
295
- }
296
-
297
- bool RatsClient::is_running() const {
298
- return running_.load();
299
- }
300
-
301
- // =========================================================================
302
- // Utility Methods
303
- // =========================================================================
304
-
305
- int RatsClient::get_listen_port() const {
306
- return listen_port_;
307
- }
308
-
309
- std::string RatsClient::get_bind_address() const {
310
- return bind_address_;
311
- }
312
-
313
- // =========================================================================
314
- // Async I/O – single-threaded event loop
315
- // =========================================================================
316
-
317
- void RatsClient::io_loop() {
318
- LOG_CLIENT_INFO("IO loop started (backend: " << poller_->name() << ")");
319
-
320
- static constexpr int MAX_EVENTS = 256;
321
- PollResult results[MAX_EVENTS];
322
-
323
- while (running_.load()) {
324
- int n = poller_->wait(results, MAX_EVENTS, IO_POLL_TIMEOUT_MS);
325
-
326
- if (n < 0) {
327
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
328
- continue;
329
- }
330
-
331
- // Collect sockets to disconnect (defer to avoid iterator issues)
332
- std::vector<socket_t> to_disconnect;
333
-
334
- for (int i = 0; i < n; ++i) {
335
- socket_t fd = results[i].fd;
336
- uint32_t events = results[i].events;
337
-
338
- // Server socket – accept incoming connections
339
- if (fd == server_socket_) {
340
- if (events & PollIn) accept_incoming();
341
- continue;
342
- }
343
-
344
- bool should_close = false;
345
-
346
- if (events & (PollErr | PollHup)) {
347
- should_close = true;
348
- }
349
-
350
- if (!should_close && (events & PollIn)) {
351
- should_close = handle_readable(fd);
352
- }
353
-
354
- if (!should_close && (events & PollOut)) {
355
- should_close = handle_writable(fd);
356
- }
357
-
358
- if (should_close) {
359
- to_disconnect.push_back(fd);
360
- }
361
- }
362
-
363
- // Handle disconnections outside the event loop
364
- for (socket_t fd : to_disconnect) {
365
- handle_disconnect(fd);
366
- }
367
- }
368
-
369
- LOG_CLIENT_INFO("IO loop ended");
370
- }
371
-
372
- // ---------------------------------------------------------------------------
373
- // accept_incoming – non-blocking accept of new TCP connections
374
- // ---------------------------------------------------------------------------
375
- void RatsClient::accept_incoming() {
376
- // Accept as many pending connections as possible (level-triggered)
377
- while (true) {
378
- socket_t client = accept_client(server_socket_);
379
- if (!is_valid_socket(client)) break;
380
-
381
- std::string peer_address = get_peer_address(client);
382
- if (peer_address.empty()) {
383
- close_socket(client);
384
- continue;
385
- }
386
-
387
- std::string ip;
388
- int port = 0;
389
- if (!parse_address_string(peer_address, ip, port)) {
390
- close_socket(client);
391
- continue;
392
- }
393
-
394
- std::string normalized = normalize_peer_address(ip, port);
395
-
396
- if (is_peer_limit_reached()) {
397
- LOG_SERVER_INFO("Peer limit reached, rejecting " << normalized);
398
- close_socket(client);
399
- continue;
400
- }
401
-
402
- if (is_already_connected_to_address(normalized)) {
403
- LOG_SERVER_DEBUG("Duplicate connection from " << normalized);
404
- close_socket(client);
405
- continue;
406
- }
407
-
408
- // Make the new socket non-blocking and register with poller
409
- set_socket_nonblocking(client);
410
-
411
- std::string initial_id = generate_temporary_peer_id(client, "incoming_from_" + peer_address);
412
-
413
- {
414
- std::lock_guard<std::mutex> lock(peers_mutex_);
415
- RatsPeer new_peer(initial_id, ip, port, client, normalized, false);
416
- new_peer.encryption_enabled = is_encryption_enabled();
417
- add_peer_unlocked(new_peer);
418
- }
419
-
420
- poller_add(client, PollIn);
421
-
422
- LOG_SERVER_INFO("Accepted incoming connection from " << normalized << " (id: " << initial_id.substr(0, 8) << "…)");
423
- }
424
- }
425
-
426
- // ---------------------------------------------------------------------------
427
- // handle_readable – drain kernel buffer, parse length-prefixed frames
428
- // Returns true if the peer should be disconnected.
429
- // ---------------------------------------------------------------------------
430
- bool RatsClient::handle_readable(socket_t socket) {
431
- // ── Phase 1: drain data & extract complete frames under lock ──────────
432
- struct PendingFrame {
433
- std::vector<uint8_t> data;
434
- RatsPeer::HandshakeState state;
435
- std::string peer_id;
436
- bool noise_encrypted;
437
- std::shared_ptr<rats::NoiseCipherState> recv_cipher;
438
- };
439
-
440
- std::vector<PendingFrame> frames;
441
- bool peer_closed = false;
442
- bool need_post_handshake = false;
443
- RatsPeer peer_copy_for_post_handshake;
444
-
445
- {
446
- std::lock_guard<std::mutex> lock(peers_mutex_);
447
- auto peer_it = find_peer_by_socket_unlocked(socket);
448
- if (peer_it == peers_.end()) return true;
449
-
450
- RatsPeer& peer = peer_it->second;
451
- auto& recv_buf = peer.io_.recv_buffer;
452
-
453
- // Non-blocking recv loop
454
- while (true) {
455
- recv_buf.ensure_space(16384);
456
- int bytes = ::recv(socket,
457
- reinterpret_cast<char*>(recv_buf.write_ptr()),
458
- static_cast<int>(recv_buf.write_space()), 0);
459
-
460
- if (bytes > 0) {
461
- recv_buf.received(static_cast<size_t>(bytes));
462
- continue;
463
- }
464
- if (bytes == 0) { peer_closed = true; break; }
465
-
466
- #ifdef _WIN32
467
- if (WSAGetLastError() == WSAEWOULDBLOCK) break;
468
- #else
469
- if (errno == EAGAIN || errno == EWOULDBLOCK) break;
470
- #endif
471
- peer_closed = true;
472
- break;
473
- }
474
-
475
- if (peer_closed && recv_buf.empty()) return true;
476
-
477
- // Parse length-prefixed frames: [4-byte network-order length][payload]
478
- while (recv_buf.size() >= 4) {
479
- uint32_t net_len;
480
- memcpy(&net_len, recv_buf.data(), 4);
481
- uint32_t msg_len = ntohl(net_len);
482
-
483
- if (msg_len > MAX_FRAME_SIZE) {
484
- LOG_CLIENT_ERROR("Frame too large (" << msg_len << " bytes) from " << peer.peer_id);
485
- return true;
486
- }
487
- if (recv_buf.size() < 4 + msg_len) break; // incomplete frame
488
-
489
- // Handle Noise handshake messages inline (fast crypto, no callbacks)
490
- if (peer.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
491
- if (!handle_noise_frame(peer)) return true;
492
- recv_buf.consume(4 + msg_len);
493
-
494
- // Check if Noise just completed
495
- if (peer.handshake_state == RatsPeer::HandshakeState::COMPLETED) {
496
- need_post_handshake = true;
497
- peer_copy_for_post_handshake = peer;
498
- }
499
- continue;
500
- }
501
-
502
- // Snapshot state for out-of-lock processing
503
- PendingFrame pf;
504
- pf.data.assign(recv_buf.data() + 4, recv_buf.data() + 4 + msg_len);
505
- pf.state = peer.handshake_state;
506
- pf.peer_id = peer.peer_id;
507
- pf.noise_encrypted = peer.is_noise_encrypted();
508
- if (pf.noise_encrypted) pf.recv_cipher = peer.recv_cipher;
509
-
510
- frames.push_back(std::move(pf));
511
- recv_buf.consume(4 + msg_len);
512
- }
513
-
514
- // Compact if >32KB wasted at front
515
- if (recv_buf.front_waste() > 32768) recv_buf.normalize();
516
- }
517
- // ── peers_mutex_ released ────────────────────────────────────────────
518
-
519
- // Deferred post-handshake completion (includes callbacks – must be outside mutex)
520
- if (need_post_handshake) {
521
- handle_post_handshake_completion(socket, peer_copy_for_post_handshake);
522
- }
523
-
524
- // ── Phase 2: process frames outside lock ─────────────────────────────
525
- for (auto& pf : frames) {
526
- // Handshake phase – RATS JSON handshake messages
527
- if (pf.state != RatsPeer::HandshakeState::COMPLETED) {
528
- if (is_handshake_message(pf.data)) {
529
- if (!handle_handshake_message(socket, pf.peer_id, pf.data)) {
530
- return true;
531
- }
532
-
533
- // Check if handshake just completed and handle Noise / post-handshake
534
- bool do_post_handshake = false;
535
- RatsPeer post_hs_copy;
536
- {
537
- std::lock_guard<std::mutex> lock(peers_mutex_);
538
- auto peer_it = find_peer_by_socket_unlocked(socket);
539
- if (peer_it == peers_.end()) return true;
540
- RatsPeer& peer = peer_it->second;
541
-
542
- if (peer.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
543
- start_noise_handshake_async(peer);
544
- } else if (peer.handshake_state == RatsPeer::HandshakeState::COMPLETED) {
545
- post_hs_copy = peer;
546
- do_post_handshake = true;
547
- }
548
- }
549
- // peers_mutex_ released – safe to invoke callbacks
550
- if (do_post_handshake) {
551
- handle_post_handshake_completion(socket, post_hs_copy);
552
- }
553
- } else {
554
- LOG_CLIENT_WARN("Non-handshake data from " << pf.peer_id << " before handshake – ignoring");
555
- }
556
- continue;
557
- }
558
-
559
- // Data phase – decrypt if needed, then dispatch
560
- std::vector<uint8_t> plaintext;
561
- if (pf.noise_encrypted && pf.recv_cipher) {
562
- if (pf.data.size() < rats::NOISE_TAG_SIZE) {
563
- LOG_CLIENT_ERROR("Encrypted frame too small from " << pf.peer_id);
564
- return true;
565
- }
566
- plaintext.resize(pf.data.size());
567
- size_t pt_len = pf.recv_cipher->decrypt_with_ad(
568
- nullptr, 0, pf.data.data(), pf.data.size(), plaintext.data());
569
- if (pt_len == 0) {
570
- LOG_CLIENT_ERROR("Decryption failed from " << pf.peer_id);
571
- return true;
572
- }
573
- plaintext.resize(pt_len);
574
- } else {
575
- plaintext = std::move(pf.data);
576
- }
577
-
578
- process_message(socket, plaintext, pf.peer_id);
579
- }
580
-
581
- return peer_closed;
582
- }
583
-
584
- // ---------------------------------------------------------------------------
585
- // handle_writable – flush the peer's send buffer to the kernel
586
- // Returns true if the peer should be disconnected.
587
- // ---------------------------------------------------------------------------
588
- bool RatsClient::handle_writable(socket_t socket) {
589
- std::lock_guard<std::mutex> lock(peers_mutex_);
590
- auto peer_it = find_peer_by_socket_unlocked(socket);
591
- if (peer_it == peers_.end()) return true;
592
-
593
- auto& send_buf = peer_it->second.io_.send_buffer;
594
-
595
- while (!send_buf.empty()) {
596
- int bytes = ::send(socket,
597
- reinterpret_cast<const char*>(send_buf.front_data()),
598
- static_cast<int>(send_buf.front_size()),
599
- #ifdef _WIN32
600
- 0
601
- #else
602
- MSG_NOSIGNAL
603
- #endif
604
- );
605
-
606
- if (bytes > 0) {
607
- send_buf.pop_front(static_cast<size_t>(bytes));
608
- continue;
609
- }
610
-
611
- if (bytes < 0) {
612
- #ifdef _WIN32
613
- if (WSAGetLastError() == WSAEWOULDBLOCK) break;
614
- #else
615
- if (errno == EAGAIN || errno == EWOULDBLOCK) break;
616
- #endif
617
- LOG_CLIENT_ERROR("Send error on socket " << socket);
618
- return true;
619
- }
620
-
621
- // bytes == 0 — shouldn't happen on a stream socket
622
- break;
623
- }
624
-
625
- // If buffer fully flushed, stop watching for PollOut
626
- if (send_buf.empty()) {
627
- std::lock_guard<std::mutex> io_lock(io_mutex_);
628
- if (poller_) poller_->modify(socket, PollIn);
629
- }
630
-
631
- return false;
632
- }
633
-
634
- // ---------------------------------------------------------------------------
635
- // handle_disconnect – clean up peer on error / hangup / close
636
- // ---------------------------------------------------------------------------
637
- void RatsClient::handle_disconnect(socket_t socket) {
638
- // Gather info before removing
639
- std::string peer_id;
640
- bool was_validated = false;
641
- RatsPeer peer_copy_for_reconnect;
642
- bool should_schedule_reconnect = false;
643
-
644
- {
645
- std::lock_guard<std::mutex> lock(peers_mutex_);
646
- auto peer_it = find_peer_by_socket_unlocked(socket);
647
- if (peer_it == peers_.end()) {
648
- // Already removed
649
- poller_remove(socket);
650
- close_socket(socket);
651
- return;
652
- }
653
- peer_id = peer_it->second.peer_id;
654
- was_validated = peer_it->second.is_handshake_completed();
655
- if (was_validated) {
656
- peer_copy_for_reconnect = peer_it->second;
657
- should_schedule_reconnect = true;
658
- }
659
- }
660
-
661
- poller_remove(socket);
662
- remove_peer(socket);
663
-
664
- if (was_validated) {
665
- if (disconnect_callback_) {
666
- disconnect_callback_(socket, peer_id);
667
- }
668
- if (gossipsub_) {
669
- gossipsub_->handle_peer_disconnected(peer_id);
670
- }
671
- if (file_transfer_manager_) {
672
- file_transfer_manager_->on_peer_disconnected(peer_id);
673
- }
674
- if (should_schedule_reconnect && running_.load()) {
675
- schedule_reconnect(peer_copy_for_reconnect);
676
- }
677
- if (running_.load()) {
678
- add_managed_thread(std::thread([this]() {
679
- if (running_.load()) save_configuration();
680
- }), "config-save-disconnect");
681
- }
682
- }
683
-
684
- close_socket(socket);
685
-
686
- LOG_CLIENT_INFO("Peer disconnected: " << peer_id);
687
- }
688
-
689
- // ---------------------------------------------------------------------------
690
- // Poller registration helpers (thread-safe via io_mutex_)
691
- // ---------------------------------------------------------------------------
692
- void RatsClient::poller_add(socket_t fd, uint32_t events) {
693
- std::lock_guard<std::mutex> lock(io_mutex_);
694
- if (poller_) poller_->add(fd, events);
695
- }
696
-
697
- void RatsClient::poller_modify(socket_t fd, uint32_t events) {
698
- std::lock_guard<std::mutex> lock(io_mutex_);
699
- if (poller_) poller_->modify(fd, events);
700
- }
701
-
702
- void RatsClient::poller_remove(socket_t fd) {
703
- std::lock_guard<std::mutex> lock(io_mutex_);
704
- if (poller_) poller_->remove(fd);
705
- }
706
-
707
- // ---------------------------------------------------------------------------
708
- // enqueue_message – build a length-prefixed frame and append to send buffer
709
- // ---------------------------------------------------------------------------
710
- bool RatsClient::enqueue_message(socket_t socket, const std::vector<uint8_t>& data) {
711
- std::lock_guard<std::mutex> lock(peers_mutex_);
712
- auto peer_it = find_peer_by_socket_unlocked(socket);
713
- if (peer_it == peers_.end()) return false;
714
- return enqueue_message_unlocked(peer_it->second, data);
715
- }
716
-
717
- bool RatsClient::enqueue_message_unlocked(RatsPeer& peer, const std::vector<uint8_t>& data) {
718
- // Build length-prefixed frame: [4-byte network-order length][payload]
719
- uint32_t net_len = htonl(static_cast<uint32_t>(data.size()));
720
-
721
- std::vector<uint8_t> frame;
722
- frame.reserve(4 + data.size());
723
- frame.insert(frame.end(),
724
- reinterpret_cast<const uint8_t*>(&net_len),
725
- reinterpret_cast<const uint8_t*>(&net_len) + 4);
726
- frame.insert(frame.end(), data.begin(), data.end());
727
-
728
- peer.io_.send_buffer.append(std::move(frame));
729
-
730
- // Arm PollOut so io_loop flushes the buffer
731
- {
732
- std::lock_guard<std::mutex> io_lock(io_mutex_);
733
- if (poller_) poller_->modify(peer.socket, PollIn | PollOut);
734
- }
735
-
736
- return true;
737
- }
738
-
739
- void RatsClient::management_loop() {
740
- LOG_CLIENT_INFO("Management loop started");
741
-
742
- auto last_thread_cleanup = std::chrono::steady_clock::now();
743
- const auto thread_cleanup_interval = std::chrono::seconds(THREAD_CLEANUP_INTERVAL_SECONDS);
744
-
745
- while (running_.load()) {
746
- // Wait for interval or until shutdown (for responsive reconnection processing)
747
- {
748
- std::unique_lock<std::mutex> lock(shutdown_mutex_);
749
- if (shutdown_cv_.wait_for(lock, std::chrono::seconds(MANAGEMENT_LOOP_INTERVAL_SECONDS), [this] { return !running_.load(); })) {
750
- break; // Exit if shutdown requested
751
- }
752
- }
753
-
754
- // Check handshake timeouts (centralized, runs once for all peers)
755
- try {
756
- check_handshake_timeouts();
757
- } catch (const std::exception& e) {
758
- LOG_CLIENT_ERROR("Exception during handshake timeout check: " << e.what());
759
- }
760
-
761
- // Process reconnection queue
762
- try {
763
- process_reconnect_queue();
764
- } catch (const std::exception& e) {
765
- LOG_CLIENT_ERROR("Exception during reconnect queue processing: " << e.what());
766
- }
767
-
768
- // Periodically cleanup finished threads (every 30 seconds)
769
- auto now = std::chrono::steady_clock::now();
770
- if (now - last_thread_cleanup >= thread_cleanup_interval) {
771
- try {
772
- cleanup_finished_threads();
773
- LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
774
- } catch (const std::exception& e) {
775
- LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
776
- }
777
- last_thread_cleanup = now;
778
- }
779
- }
780
-
781
- LOG_CLIENT_INFO("Management loop ended");
782
- }
783
-
784
- void RatsClient::handle_post_handshake_completion(socket_t socket, const RatsPeer& peer_copy) {
785
- // Remove from reconnection queue (successful connection)
786
- remove_from_reconnect_queue(peer_copy.peer_id);
787
-
788
- // Connection callback
789
- if (connection_callback_) {
790
- connection_callback_(socket, peer_copy.peer_id);
791
- }
792
-
793
- // GossipSub notification
794
- if (gossipsub_) {
795
- gossipsub_->handle_peer_connected(peer_copy.peer_id);
796
- }
797
-
798
- #ifdef RATS_STORAGE
799
- // Storage manager notification
800
- if (storage_manager_) {
801
- storage_manager_->on_peer_connected(peer_copy.peer_id);
802
- }
803
- #endif
804
-
805
- // Peer exchange broadcast
806
- broadcast_peer_exchange_message(peer_copy);
807
-
808
- // Request peers from newly connected peer (outgoing only)
809
- if (peer_copy.is_outgoing) {
810
- send_peers_request(socket, peer_copy.peer_id);
811
- }
812
-
813
- // Save configuration
814
- if (running_.load()) {
815
- add_managed_thread(std::thread([this]() {
816
- if (running_.load()) {
817
- save_configuration();
818
- }
819
- }), "config-save");
820
- }
821
- }
822
-
823
- void RatsClient::process_message(socket_t socket, const std::vector<uint8_t>& data, const std::string& initial_peer_id) {
824
- MessageHeader header;
825
- std::vector<uint8_t> payload;
826
-
827
- if (!parse_message_with_header(data, header, payload)) {
828
- LOG_CLIENT_WARN("No header found in message from " << initial_peer_id);
829
- return;
830
- }
831
-
832
- std::string peer_id = get_peer_id(socket);
833
-
834
- switch (header.type) {
835
- case MessageDataType::BINARY: {
836
- LOG_CLIENT_DEBUG("Received BINARY message from " << peer_id << " (payload size: " << payload.size() << ")");
837
- bool handled = false;
838
- if (file_transfer_manager_) {
839
- handled = file_transfer_manager_->handle_binary_data(peer_id, payload);
840
- }
841
- if (!handled && binary_data_callback_) {
842
- binary_data_callback_(socket, peer_id, payload);
843
- }
844
- break;
845
- }
846
-
847
- case MessageDataType::STRING: {
848
- LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
849
- if (string_data_callback_) {
850
- std::string string_data(payload.begin(), payload.end());
851
- string_data_callback_(socket, peer_id, string_data);
852
- }
853
- break;
854
- }
855
-
856
- case MessageDataType::JSON: {
857
- LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
858
- try {
859
- nlohmann::json json_msg = nlohmann::json::parse(payload.begin(), payload.end());
860
- if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
861
- handle_rats_message(socket, peer_id, json_msg);
862
- } else if (json_data_callback_) {
863
- json_data_callback_(socket, peer_id, json_msg);
864
- }
865
- } catch (const nlohmann::json::exception& e) {
866
- LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id << ": " << e.what());
867
- }
868
- break;
869
- }
870
-
871
- default:
872
- LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
873
- break;
874
- }
875
- }
876
-
877
- // Handshake protocol implementation
878
- std::string RatsClient::create_handshake_message(const std::string& message_type, const std::string& our_peer_id) const {
879
- auto now = std::chrono::high_resolution_clock::now();
880
- auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
881
-
882
- // Use nlohmann::json for proper JSON serialization
883
- nlohmann::json handshake_msg;
884
- {
885
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
886
- handshake_msg["protocol"] = custom_protocol_name_;
887
- handshake_msg["version"] = custom_protocol_version_;
888
- }
889
- handshake_msg["peer_id"] = our_peer_id;
890
- handshake_msg["message_type"] = message_type;
891
- handshake_msg["timestamp"] = timestamp;
892
- handshake_msg["encryption_enabled"] = is_encryption_enabled();
893
- handshake_msg["listen_port"] = listen_port_;
894
-
895
- return handshake_msg.dump();
896
- }
897
-
898
- bool RatsClient::parse_handshake_message(const std::vector<uint8_t>& data, HandshakeMessage& out_msg) const {
899
- try {
900
- // Use nlohmann::json with iterators to avoid string conversion
901
- nlohmann::json json_msg = nlohmann::json::parse(data.begin(), data.end());
902
-
903
- // Clear the output structure
904
- out_msg = HandshakeMessage{};
905
-
906
- // Extract fields using nlohmann::json
907
- out_msg.protocol = json_msg.value("protocol", "");
908
- out_msg.version = json_msg.value("version", "");
909
- out_msg.peer_id = json_msg.value("peer_id", "");
910
- out_msg.message_type = json_msg.value("message_type", "");
911
- // Tolerate missing timestamp to avoid hard dependency on remote system clock
912
- out_msg.timestamp = json_msg.value("timestamp", static_cast<int64_t>(0));
913
- // Parse encryption_enabled (default to false for backward compatibility)
914
- out_msg.encryption_enabled = json_msg.value("encryption_enabled", false);
915
- // Parse listen_port (default to 0 for backward compatibility with older clients)
916
- out_msg.listen_port = json_msg.value("listen_port", static_cast<uint16_t>(0));
917
-
918
- return true;
919
-
920
- } catch (const nlohmann::json::exception& e) {
921
- LOG_CLIENT_ERROR("Failed to parse handshake message: " << e.what());
922
- return false;
923
- } catch (const std::exception& e) {
924
- LOG_CLIENT_ERROR("Failed to parse handshake message: " << e.what());
925
- return false;
926
- }
927
- }
928
-
929
- bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
930
- std::string expected_protocol;
931
- std::string expected_version;
932
- {
933
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
934
- expected_protocol = custom_protocol_name_;
935
- expected_version = custom_protocol_version_;
936
- }
937
-
938
- // Validate protocol
939
- if (msg.protocol != expected_protocol) {
940
- LOG_CLIENT_WARN("Invalid handshake protocol: " << msg.protocol << " (expected: " << expected_protocol << ")");
941
- return false;
942
- }
943
-
944
- // Validate version (for now, only accept exact version match)
945
- if (msg.version != expected_version) {
946
- LOG_CLIENT_WARN("Unsupported protocol version: " << msg.version << " (expected: " << expected_version << ")");
947
- return false;
948
- }
949
-
950
- // Validate message type
951
- if (msg.message_type != "handshake") {
952
- LOG_CLIENT_WARN("Invalid handshake message type: " << msg.message_type);
953
- return false;
954
- }
955
-
956
- // Validate peer_id (must not be empty)
957
- if (msg.peer_id.empty()) {
958
- LOG_CLIENT_WARN("Empty peer_id in handshake message");
959
- return false;
960
- }
961
-
962
- // Soft-validate timestamp to avoid rejecting valid peers due to clock skew
963
- if (msg.timestamp == 0) {
964
- LOG_CLIENT_WARN("Handshake missing timestamp; accepting to avoid clock-skew rejection");
965
- } else {
966
- auto now = std::chrono::high_resolution_clock::now();
967
- auto current_timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
968
- int64_t time_diff = std::abs(current_timestamp - msg.timestamp);
969
- if (time_diff > TIMESTAMP_SKEW_TOLERANCE_MS) {
970
- LOG_CLIENT_WARN("Handshake timestamp skew " << time_diff << "ms exceeds " << TIMESTAMP_SKEW_TOLERANCE_MS << "ms; accepting to be tolerant of clock skew");
971
- }
972
- }
973
-
974
- return true;
975
- }
976
-
977
- bool RatsClient::is_handshake_message(const std::vector<uint8_t>& data) const {
978
- try {
979
- // Check if message has our message header (starts with "RATS" magic)
980
- MessageHeader header;
981
- std::vector<uint8_t> payload;
982
-
983
- if (parse_message_with_header(data, header, payload)) {
984
- // Message has valid header - extract the JSON payload
985
- if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
986
- // Parse the JSON message directly from payload
987
- nlohmann::json json_msg = nlohmann::json::parse(payload.begin(), payload.end());
988
- std::string expected_protocol;
989
- {
990
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
991
- expected_protocol = custom_protocol_name_;
992
- }
993
- return json_msg.value("protocol", "") == expected_protocol &&
994
- json_msg.value("message_type", "") == "handshake";
995
- }
996
- // Handshake messages should be string/JSON type
997
- return false;
998
- }
999
- // Message has no header
1000
- return false;
1001
- } catch (const std::exception&) {
1002
- return false;
1003
- }
1004
- }
1005
-
1006
- bool RatsClient::send_handshake_unlocked(RatsPeer& peer, const std::string& our_peer_id) {
1007
- std::string handshake_msg = create_handshake_message("handshake", our_peer_id);
1008
- LOG_CLIENT_DEBUG("Sending handshake to " << peer.peer_id << ": " << handshake_msg);
1009
-
1010
- // Handshakes are always unencrypted – enqueue into the peer's send buffer
1011
- std::vector<uint8_t> binary_data(handshake_msg.begin(), handshake_msg.end());
1012
- std::vector<uint8_t> message_with_header = create_message_with_header(binary_data, MessageDataType::STRING);
1013
-
1014
- if (!enqueue_message_unlocked(peer, message_with_header)) {
1015
- LOG_CLIENT_ERROR("Failed to enqueue handshake for " << peer.peer_id);
1016
- return false;
1017
- }
1018
-
1019
- peer.handshake_state = RatsPeer::HandshakeState::SENT;
1020
- peer.handshake_start_time = std::chrono::steady_clock::now();
1021
-
1022
- return true;
1023
- }
1024
-
1025
- bool RatsClient::handle_handshake_message(socket_t socket, const std::string& initial_peer_id, const std::vector<uint8_t>& data) {
1026
- // Extract JSON payload from message header
1027
- MessageHeader header;
1028
- std::vector<uint8_t> payload;
1029
-
1030
- if (!parse_message_with_header(data, header, payload)) {
1031
- LOG_CLIENT_ERROR("Failed to parse handshake message header from " << initial_peer_id);
1032
- return false;
1033
- }
1034
-
1035
- // Message has valid header - check the type
1036
- if (header.type != MessageDataType::STRING && header.type != MessageDataType::JSON) {
1037
- LOG_CLIENT_ERROR("Invalid message type for handshake: " << static_cast<int>(header.type));
1038
- return false;
1039
- }
1040
-
1041
- // Parse handshake message directly from payload (no string conversion)
1042
- HandshakeMessage handshake_msg;
1043
- if (!parse_handshake_message(payload, handshake_msg)) {
1044
- LOG_CLIENT_ERROR("Failed to parse handshake message from " << initial_peer_id);
1045
- return false;
1046
- }
1047
-
1048
- if (!validate_handshake_message(handshake_msg)) {
1049
- LOG_CLIENT_ERROR("Invalid handshake message from " << initial_peer_id);
1050
- return false;
1051
- }
1052
-
1053
- if (handshake_msg.peer_id == get_our_peer_id()) {
1054
- LOG_CLIENT_INFO("Received handshake from ourselves, ignoring");
1055
- return false;
1056
- }
1057
-
1058
- LOG_CLIENT_INFO("Received valid handshake from " << initial_peer_id
1059
- << " (peer_id: " << handshake_msg.peer_id << ")");
1060
-
1061
- std::lock_guard<std::mutex> lock(peers_mutex_);
1062
- auto peer_it = find_peer_by_socket_unlocked(socket);
1063
- if (peer_it == peers_.end()) {
1064
- LOG_CLIENT_ERROR("Peer " << initial_peer_id << " not found for socket " << socket);
1065
- return false;
1066
- }
1067
-
1068
- if (peers_.find(handshake_msg.peer_id) != peers_.end()) {
1069
- LOG_CLIENT_INFO("Peer " << handshake_msg.peer_id << " already connected, closing duplicate connection");
1070
- // This is a duplicate connection - the existing connection should remain stable
1071
- // Return false to close this duplicate connection, but this is expected behavior
1072
- return false;
1073
- }
1074
-
1075
- // Store old peer ID for mapping updates
1076
- std::string old_peer_id = peer_it->second.peer_id;
1077
-
1078
- // Update peer mappings with new peer_id if it changed
1079
- if (old_peer_id != handshake_msg.peer_id) {
1080
- // Move the peer (preserves io_ context with send/recv buffers)
1081
- RatsPeer peer_moved = std::move(peer_it->second);
1082
- peers_.erase(peer_it);
1083
-
1084
- peer_moved.peer_id = handshake_msg.peer_id;
1085
-
1086
- // Use emplace to avoid extra copy/move
1087
- auto [new_it, ok] = peers_.emplace(peer_moved.peer_id, std::move(peer_moved));
1088
- socket_to_peer_id_[socket] = new_it->second.peer_id;
1089
- address_to_peer_id_[new_it->second.normalized_address] = new_it->second.peer_id;
1090
-
1091
- peer_it = new_it;
1092
- }
1093
-
1094
- RatsPeer& peer = peer_it->second;
1095
-
1096
- // Store remote peer information
1097
- peer.version = handshake_msg.version;
1098
-
1099
- // Determine if encryption should be used for this connection
1100
- // Encryption is enabled only if BOTH sides support it
1101
- bool local_encryption = is_encryption_enabled();
1102
- bool remote_encryption = handshake_msg.encryption_enabled;
1103
- peer.encryption_enabled = local_encryption && remote_encryption;
1104
-
1105
- LOG_CLIENT_INFO("Encryption negotiation: local=" << local_encryption
1106
- << ", remote=" << remote_encryption
1107
- << ", result=" << peer.encryption_enabled);
1108
-
1109
- // For incoming connections, update port to the peer's actual listen port
1110
- // This is critical for peer exchange to work correctly
1111
- if (!peer.is_outgoing && handshake_msg.listen_port > 0) {
1112
- // Remove old address mapping
1113
- address_to_peer_id_.erase(peer.normalized_address);
1114
-
1115
- // Update port and normalized address
1116
- peer.port = handshake_msg.listen_port;
1117
- peer.normalized_address = normalize_peer_address(peer.ip, peer.port);
1118
-
1119
- // Add new address mapping
1120
- address_to_peer_id_[peer.normalized_address] = peer.peer_id;
1121
-
1122
- LOG_CLIENT_INFO("Updated incoming peer port to listen_port: " << peer.ip << ":" << peer.port);
1123
- }
1124
-
1125
- // Simplified handshake logic - just one message type
1126
- if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
1127
- // This is an incoming handshake - send our handshake back
1128
- if (send_handshake_unlocked(peer, get_our_peer_id())) {
1129
- // If encryption is enabled, we need to do Noise handshake first
1130
- // Set NOISE_PENDING to prevent other threads from sending messages
1131
- if (peer.encryption_enabled) {
1132
- peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1133
- LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
1134
- } else {
1135
- peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1136
- validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
1137
- log_handshake_completion_unlocked(peer);
1138
- }
1139
-
1140
- // Append to historical peers file after successful connection
1141
- append_peer_to_historical_file(peer);
1142
-
1143
- return true;
1144
- } else {
1145
- peer.handshake_state = RatsPeer::HandshakeState::FAILED;
1146
- LOG_CLIENT_ERROR("Failed to send handshake response to " << initial_peer_id);
1147
- return false;
1148
- }
1149
- } else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
1150
- // This is a response to our handshake
1151
- // If encryption is enabled, we need to do Noise handshake first
1152
- // Set NOISE_PENDING to prevent other threads from sending messages
1153
- if (peer.encryption_enabled) {
1154
- peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1155
- LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
1156
- } else {
1157
- peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1158
- validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
1159
- log_handshake_completion_unlocked(peer);
1160
- }
1161
-
1162
- // Append to historical peers file after successful connection
1163
- append_peer_to_historical_file(peer);
1164
-
1165
- return true;
1166
- } else {
1167
- LOG_CLIENT_WARN("Received handshake from " << initial_peer_id << " but handshake state is " << static_cast<int>(peer.handshake_state));
1168
- return false;
1169
- }
1170
- }
1171
-
1172
- void RatsClient::check_handshake_timeouts() {
1173
- std::lock_guard<std::mutex> lock(peers_mutex_);
1174
- auto now = std::chrono::steady_clock::now();
1175
-
1176
- std::vector<std::string> peers_to_remove;
1177
-
1178
- for (auto& pair : peers_) {
1179
- RatsPeer& peer = pair.second;
1180
-
1181
- if (peer.handshake_state != RatsPeer::HandshakeState::COMPLETED &&
1182
- peer.handshake_state != RatsPeer::HandshakeState::FAILED) {
1183
-
1184
- auto handshake_duration = std::chrono::duration_cast<std::chrono::seconds>(now - peer.handshake_start_time);
1185
-
1186
- if (handshake_duration.count() > HANDSHAKE_TIMEOUT_SECONDS) {
1187
- LOG_CLIENT_WARN("Handshake timeout for peer " << peer.peer_id << " after " << handshake_duration.count() << " seconds");
1188
- peer.handshake_state = RatsPeer::HandshakeState::FAILED;
1189
- peers_to_remove.push_back(peer.peer_id);
1190
- }
1191
- }
1192
- }
1193
-
1194
- // Remove timed out peers
1195
- for (const auto& peer_id : peers_to_remove) {
1196
- auto peer_it = peers_.find(peer_id);
1197
- if (peer_it != peers_.end()) {
1198
- socket_t socket = peer_it->second.socket;
1199
- LOG_CLIENT_INFO("Disconnecting peer " << peer_id << " due to handshake timeout");
1200
-
1201
- remove_peer_by_id_unlocked(peer_id);
1202
- poller_remove(socket);
1203
- close_socket(socket);
1204
- }
1205
- }
1206
- }
1207
-
1208
- // =========================================================================
1209
- // Connection Management
1210
- // =========================================================================
1211
-
1212
- bool RatsClient::connect_to_peer(const std::string& host, int port) {
1213
- if (!running_.load()) {
1214
- LOG_CLIENT_ERROR("RatsClient is not running");
1215
- return false;
1216
- }
1217
-
1218
- LOG_CLIENT_INFO("Connecting to peer " << host << ":" << port);
1219
-
1220
- // Check if we should ignore this address (self-connection prevention)
1221
- if (should_ignore_peer(host, port)) {
1222
- LOG_CLIENT_DEBUG("Ignoring connection to blocked address: " << host << ":" << port);
1223
- return false;
1224
- }
1225
-
1226
- // Check if we're already connected
1227
- std::string normalized_address = normalize_peer_address(host, port);
1228
- if (is_already_connected_to_address(normalized_address)) {
1229
- LOG_CLIENT_DEBUG("Already connected to " << host << ":" << port);
1230
- return true; // Consider it a success since we're already connected
1231
- }
1232
-
1233
- // Check peer limit
1234
- if (is_peer_limit_reached()) {
1235
- LOG_CLIENT_WARN("Peer limit reached, cannot connect to " << host << ":" << port);
1236
- return false;
1237
- }
1238
-
1239
- // Create TCP connection with timeout (blocking connect is done on a managed thread
1240
- // to avoid blocking the caller, then the connected socket is handed to the IO loop).
1241
- add_managed_thread(std::thread([this, host, port, normalized_address]() {
1242
- socket_t client_socket = create_tcp_client(host, port, TCP_CONNECT_TIMEOUT_MS);
1243
- if (!is_valid_socket(client_socket)) {
1244
- LOG_CLIENT_DEBUG("Failed to connect to " << host << ":" << port);
1245
- return;
1246
- }
1247
-
1248
- LOG_CLIENT_INFO("TCP connected to " << host << ":" << port);
1249
-
1250
- // Switch to non-blocking for the IO poller
1251
- set_socket_nonblocking(client_socket);
1252
-
1253
- std::string initial_peer_id = generate_temporary_peer_id(client_socket, normalized_address);
1254
-
1255
- {
1256
- std::lock_guard<std::mutex> lock(peers_mutex_);
1257
-
1258
- // Re-check peer limit and duplicate (could have changed while connecting)
1259
- // NOTE: Use unlocked variant — peers_mutex_ is already held!
1260
- if (get_peer_count_unlocked() >= max_peers_) {
1261
- LOG_CLIENT_DEBUG("connect_to_peer: peer limit reached after TCP connect, aborting fd=" << client_socket);
1262
- close_socket(client_socket);
1263
- return;
1264
- }
1265
- if (address_to_peer_id_.find(normalized_address) != address_to_peer_id_.end()) {
1266
- LOG_CLIENT_DEBUG("connect_to_peer: duplicate address " << normalized_address << " after TCP connect, aborting fd=" << client_socket);
1267
- close_socket(client_socket);
1268
- return;
1269
- }
1270
-
1271
- RatsPeer new_peer(initial_peer_id, host, static_cast<uint16_t>(port),
1272
- client_socket, normalized_address, true);
1273
- new_peer.encryption_enabled = is_encryption_enabled();
1274
- add_peer_unlocked(new_peer);
1275
-
1276
- // Send initial handshake (enqueued into send buffer)
1277
- auto peer_it = peers_.find(initial_peer_id);
1278
- if (peer_it != peers_.end()) {
1279
- if (!send_handshake_unlocked(peer_it->second, get_our_peer_id())) {
1280
- LOG_CLIENT_ERROR("Failed to enqueue handshake for outgoing connection to " << host << ":" << port);
1281
- remove_peer_by_id_unlocked(initial_peer_id);
1282
- close_socket(client_socket);
1283
- return;
1284
- }
1285
- }
1286
- }
1287
-
1288
- // Register with poller – PollIn for reads, PollOut to flush the queued handshake
1289
- poller_add(client_socket, PollIn | PollOut);
1290
-
1291
- LOG_CLIENT_INFO("Outgoing connection to " << host << ":" << port << " registered with IO poller");
1292
- }), "connect-" + host + ":" + std::to_string(port));
1293
-
1294
- return true;
1295
- }
1296
-
1297
- void RatsClient::mark_manual_disconnect(const std::string& peer_id) {
1298
- std::lock_guard<std::mutex> lock(reconnect_mutex_);
1299
- manual_disconnect_peers_.insert(peer_id);
1300
- reconnect_queue_.erase(peer_id);
1301
- }
1302
-
1303
- void RatsClient::disconnect_peer(socket_t socket) {
1304
- std::string peer_id = get_peer_id(socket);
1305
- if (!peer_id.empty()) {
1306
- mark_manual_disconnect(peer_id);
1307
- }
1308
- poller_remove(socket);
1309
- remove_peer(socket);
1310
- close_socket(socket);
1311
- }
1312
-
1313
- void RatsClient::disconnect_peer_by_id(const std::string& peer_id) {
1314
- mark_manual_disconnect(peer_id);
1315
- socket_t socket = get_peer_socket_by_id(peer_id);
1316
- if (is_valid_socket(socket)) {
1317
- poller_remove(socket);
1318
- remove_peer(socket);
1319
- close_socket(socket);
1320
- }
1321
- }
1322
-
1323
- // Peer lookup helpers (assumes peers_mutex_ is already locked)
1324
- std::unordered_map<std::string, RatsPeer>::iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) {
1325
- auto sock_it = socket_to_peer_id_.find(socket);
1326
- if (sock_it != socket_to_peer_id_.end()) {
1327
- return peers_.find(sock_it->second);
1328
- }
1329
- return peers_.end();
1330
- }
1331
-
1332
- std::unordered_map<std::string, RatsPeer>::const_iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) const {
1333
- auto sock_it = socket_to_peer_id_.find(socket);
1334
- if (sock_it != socket_to_peer_id_.end()) {
1335
- return peers_.find(sock_it->second);
1336
- }
1337
- return peers_.end();
1338
- }
1339
-
1340
- // Helper methods for peer management
1341
- void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
1342
- // Assumes peers_mutex_ is already locked
1343
- peers_[peer.peer_id] = peer;
1344
- socket_to_peer_id_[peer.socket] = peer.peer_id;
1345
- address_to_peer_id_[peer.normalized_address] = peer.peer_id;
1346
- }
1347
-
1348
- void RatsClient::remove_peer(socket_t socket) {
1349
- std::lock_guard<std::mutex> lock(peers_mutex_);
1350
- auto peer_it = find_peer_by_socket_unlocked(socket);
1351
- if (peer_it != peers_.end()) {
1352
- remove_peer_by_id_unlocked(peer_it->second.peer_id);
1353
- }
1354
- }
1355
-
1356
- void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
1357
- // Assumes peers_mutex_ is already locked
1358
-
1359
- // Make a copy of peer_id to avoid use-after-free if the reference points to memory that gets freed
1360
- std::string peer_id_copy = peer_id;
1361
-
1362
- auto it = peers_.find(peer_id_copy);
1363
- if (it != peers_.end()) {
1364
- // Decrement validated peer count if this peer had completed handshake
1365
- if (it->second.is_handshake_completed()) {
1366
- validated_peer_count_.fetch_sub(1, std::memory_order_relaxed);
1367
- }
1368
-
1369
- // Copy the values we need before erasing to avoid use-after-free
1370
- socket_t peer_socket = it->second.socket;
1371
- std::string peer_normalized_address = it->second.normalized_address;
1372
-
1373
- socket_to_peer_id_.erase(peer_socket);
1374
- address_to_peer_id_.erase(peer_normalized_address);
1375
- peers_.erase(it);
1376
- }
1377
- }
1378
-
1379
- bool RatsClient::is_already_connected_to_address(const std::string& normalized_address) const {
1380
- std::lock_guard<std::mutex> lock(peers_mutex_);
1381
- return address_to_peer_id_.find(normalized_address) != address_to_peer_id_.end();
1382
- }
1383
-
1384
- void RatsClient::add_ignored_address(const std::string& ip_address) {
1385
- std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1386
-
1387
- auto [it, inserted] = local_interface_addresses_.insert(ip_address);
1388
- if (inserted) {
1389
- LOG_CLIENT_INFO("Added " << ip_address << " to ignore list");
1390
- } else {
1391
- LOG_CLIENT_DEBUG("IP address " << ip_address << " already in ignore list");
1392
- }
1393
- }
1394
-
1395
- //common localhost addresses
1396
- static constexpr std::array<std::string_view,5> localhost_addrs{"127.0.0.1", "::1", "0.0.0.0", "::", "localhost"};
1397
-
1398
- // Local interface address blocking methods
1399
- void RatsClient::initialize_local_addresses() {
1400
- std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1401
-
1402
- // (Re)enumerate the host's interface addresses. This is also called on every
1403
- // network change, so it must be a diff, not a blind insert: drop auto-detected
1404
- // addresses that have disappeared (a removed IP must stop being treated as
1405
- // "ourselves"), while preserving localhost entries and any externally-
1406
- // discovered / manually-ignored addresses (STUN reflexive, mapped external IP,
1407
- // user add_ignored_address()), which live in the same set.
1408
- auto addrs = network_utils::get_local_interface_addresses();
1409
- std::unordered_set<std::string> new_auto(addrs.begin(), addrs.end());
1410
-
1411
- for (const auto& old : auto_interface_addresses_) {
1412
- if (new_auto.find(old) == new_auto.end()) {
1413
- local_interface_addresses_.erase(old);
1414
- }
1415
- }
1416
- for (const auto& addr : addrs) {
1417
- local_interface_addresses_.insert(addr);
1418
- }
1419
- for (const auto& addr : localhost_addrs) {
1420
- local_interface_addresses_.emplace(std::string(addr));
1421
- }
1422
- auto_interface_addresses_ = std::move(new_auto);
1423
-
1424
- LOG_CLIENT_INFO("Local interface addresses: " << local_interface_addresses_.size()
1425
- << " blocked (" << addrs.size() << " from interfaces)");
1426
- for (const auto& addr : local_interface_addresses_) {
1427
- LOG_CLIENT_DEBUG(" - " << addr);
1428
- }
1429
- }
1430
-
1431
- bool RatsClient::is_blocked_address(const std::string& ip_address) const {
1432
- std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1433
- return local_interface_addresses_.count(ip_address) > 0;
1434
- }
1435
-
1436
- bool RatsClient::can_connect_to_peer(const std::string& ip, int port) const {
1437
- if (should_ignore_peer(ip, port)) {
1438
- LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - blocked address");
1439
- return false;
1440
- }
1441
-
1442
- std::string normalized_address = normalize_peer_address(ip, port);
1443
- if (is_already_connected_to_address(normalized_address)) {
1444
- LOG_CLIENT_DEBUG("Already connected to " << normalized_address);
1445
- return false;
1446
- }
1447
-
1448
- if (is_peer_limit_reached()) {
1449
- LOG_CLIENT_DEBUG("Peer limit reached, cannot connect to " << ip << ":" << port);
1450
- return false;
1451
- }
1452
-
1453
- return true;
1454
- }
1455
-
1456
- bool RatsClient::should_ignore_peer(const std::string& ip, int port) const {
1457
- // Check if this is a well-known localhost address
1458
- bool is_localhost = std::find(localhost_addrs.begin(), localhost_addrs.end(), ip) != localhost_addrs.end();
1459
-
1460
- if (is_localhost) {
1461
- // Block self-connections (same port on localhost)
1462
- if (port == listen_port_) {
1463
- LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - localhost with same port");
1464
- return true;
1465
- }
1466
- // Allow localhost on different ports (for testing)
1467
- LOG_CLIENT_DEBUG("Allowing localhost peer " << ip << ":" << port << " on different port");
1468
- return false;
1469
- }
1470
-
1471
- // Block non-localhost local interface addresses
1472
- if (is_blocked_address(ip)) {
1473
- LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - matches local interface address");
1474
- return true;
1475
- }
1476
-
1477
- return false;
1478
- }
1479
-
1480
- // =========================================================================
1481
- // Data Transmission Methods
1482
- // =========================================================================
1483
-
1484
- // Helper method to create a message with header
1485
- std::vector<uint8_t> RatsClient::create_message_with_header(const std::vector<uint8_t>& payload, MessageDataType type) {
1486
- MessageHeader header(type);
1487
- std::vector<uint8_t> header_bytes = header.serialize();
1488
-
1489
- // Combine header + payload
1490
- std::vector<uint8_t> message;
1491
- message.reserve(header_bytes.size() + payload.size());
1492
- message.insert(message.end(), header_bytes.begin(), header_bytes.end());
1493
- message.insert(message.end(), payload.begin(), payload.end());
1494
-
1495
- return message;
1496
- }
1497
-
1498
- // Helper method to parse message header and extract payload
1499
- bool RatsClient::parse_message_with_header(const std::vector<uint8_t>& message, MessageHeader& header, std::vector<uint8_t>& payload) const {
1500
- // Check if message is large enough to contain header
1501
- if (message.size() < MessageHeader::HEADER_SIZE) {
1502
- LOG_CLIENT_DEBUG("Message too small to contain header: " << message.size() << " bytes");
1503
- return false;
1504
- }
1505
-
1506
- // Extract header bytes
1507
- std::vector<uint8_t> header_bytes(message.begin(), message.begin() + MessageHeader::HEADER_SIZE);
1508
-
1509
- // Parse header
1510
- if (!MessageHeader::deserialize(header_bytes, header)) {
1511
- LOG_CLIENT_DEBUG("Failed to parse message header - invalid magic number or format");
1512
- return false;
1513
- }
1514
-
1515
- // Validate header
1516
- if (!header.is_valid_type()) {
1517
- LOG_CLIENT_WARN("Invalid message data type: " << static_cast<int>(header.type));
1518
- return false;
1519
- }
1520
-
1521
- // Extract payload
1522
- payload.assign(message.begin() + MessageHeader::HEADER_SIZE, message.end());
1523
-
1524
- return true;
1525
- }
1526
-
1527
- // Async send – enqueues header + (optionally encrypted) payload into the peer's
1528
- // ChainedSendBuffer. Does NOT require peers_mutex_; caller passes cached peer data.
1529
- // The shared_ptr keeps the cipher alive even if the peer is removed concurrently.
1530
- bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector<uint8_t>& data,
1531
- MessageDataType message_type,
1532
- std::shared_ptr<rats::NoiseCipherState> send_cipher,
1533
- const std::string& peer_id_for_logging) {
1534
- if (!running_.load()) {
1535
- return false;
1536
- }
1537
-
1538
- // Create message with specified header type
1539
- std::vector<uint8_t> message_with_header = create_message_with_header(data, message_type);
1540
-
1541
- if (send_cipher) {
1542
- // Encrypt the message before enqueuing
1543
- std::vector<uint8_t> ciphertext(message_with_header.size() + rats::NOISE_TAG_SIZE);
1544
- size_t ct_len = send_cipher->encrypt_with_ad(
1545
- nullptr, 0,
1546
- message_with_header.data(), message_with_header.size(),
1547
- ciphertext.data()
1548
- );
1549
-
1550
- if (ct_len == 0) {
1551
- LOG_CLIENT_ERROR("Failed to encrypt message for peer: " << peer_id_for_logging);
1552
- return false;
1553
- }
1554
-
1555
- ciphertext.resize(ct_len);
1556
- LOG_CLIENT_DEBUG("Enqueuing encrypted message for " << peer_id_for_logging << " (" << ct_len << " bytes)");
1557
-
1558
- return enqueue_message(socket, ciphertext);
1559
- }
1560
-
1561
- // Unencrypted path
1562
- return enqueue_message(socket, message_with_header);
1563
- }
1564
-
1565
- bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>& data, MessageDataType message_type) {
1566
- if (!running_.load()) {
1567
- return false;
1568
- }
1569
-
1570
- // Cache peer data under lock, then release lock before sending
1571
- std::string peer_id;
1572
- std::shared_ptr<rats::NoiseCipherState> send_cipher;
1573
-
1574
- {
1575
- std::lock_guard<std::mutex> lock(peers_mutex_);
1576
- auto peer_it = find_peer_by_socket_unlocked(socket);
1577
- if (peer_it != peers_.end()) {
1578
- peer_id = peer_it->second.peer_id;
1579
- if (peer_it->second.is_noise_encrypted()) {
1580
- send_cipher = peer_it->second.send_cipher; // shared_ptr copy keeps cipher alive
1581
- }
1582
- }
1583
- }
1584
-
1585
- // peers_mutex_ released -- safe to do potentially slow TCP send
1586
- return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
1587
- }
1588
-
1589
- bool RatsClient::send_string_to_peer(socket_t socket, const std::string& data) {
1590
- // Convert string to binary and use the primary send_binary_to_peer method
1591
- std::vector<uint8_t> binary_data(data.begin(), data.end());
1592
- return send_binary_to_peer(socket, binary_data, MessageDataType::STRING);
1593
- }
1594
-
1595
- std::vector<uint8_t> RatsClient::json_to_binary(const nlohmann::json& data) {
1596
- std::string s = data.dump();
1597
- return {s.begin(), s.end()};
1598
- }
1599
-
1600
- bool RatsClient::send_json_to_peer(socket_t socket, const nlohmann::json& data) {
1601
- try {
1602
- return send_binary_to_peer(socket, json_to_binary(data), MessageDataType::JSON);
1603
- } catch (const nlohmann::json::exception& e) {
1604
- LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
1605
- return false;
1606
- }
1607
- }
1608
-
1609
- bool RatsClient::send_binary_to_peer_id(const std::string& peer_id, const std::vector<uint8_t>& data, MessageDataType message_type) {
1610
- // Cache peer data under lock, then release before sending
1611
- socket_t socket;
1612
- std::shared_ptr<rats::NoiseCipherState> send_cipher;
1613
-
1614
- {
1615
- std::lock_guard<std::mutex> lock(peers_mutex_);
1616
- auto it = peers_.find(peer_id);
1617
- if (it == peers_.end() || !it->second.is_handshake_completed()) {
1618
- return false;
1619
- }
1620
- socket = it->second.socket;
1621
- if (it->second.is_noise_encrypted()) {
1622
- send_cipher = it->second.send_cipher; // shared_ptr copy keeps cipher alive
1623
- }
1624
- }
1625
-
1626
- // peers_mutex_ released -- safe to do potentially slow TCP send
1627
- return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
1628
- }
1629
-
1630
- bool RatsClient::send_string_to_peer_id(const std::string& peer_id, const std::string& data) {
1631
- // Convert string to binary and use primary binary method with STRING type
1632
- std::vector<uint8_t> binary_data(data.begin(), data.end());
1633
- return send_binary_to_peer_id(peer_id, binary_data, MessageDataType::STRING);
1634
- }
1635
-
1636
- bool RatsClient::send_json_to_peer_id(const std::string& peer_id, const nlohmann::json& data) {
1637
- try {
1638
- return send_binary_to_peer_id(peer_id, json_to_binary(data), MessageDataType::JSON);
1639
- } catch (const nlohmann::json::exception& e) {
1640
- LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
1641
- return false;
1642
- }
1643
- }
1644
-
1645
- int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
1646
- try {
1647
- return broadcast_binary_to_peers(json_to_binary(data), MessageDataType::JSON);
1648
- } catch (const nlohmann::json::exception& e) {
1649
- LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
1650
- return 0;
1651
- }
1652
- }
1653
-
1654
- int RatsClient::broadcast_binary_to_peers(const std::vector<uint8_t>& data, MessageDataType message_type) {
1655
- if (!running_.load()) {
1656
- return 0;
1657
- }
1658
-
1659
- // Collect targets under lock, then enqueue outside
1660
- std::vector<PeerSendTarget> targets;
1661
- {
1662
- std::lock_guard<std::mutex> lock(peers_mutex_);
1663
- targets.reserve(peers_.size());
1664
- for (const auto& [id, peer] : peers_) {
1665
- if (peer.is_handshake_completed()) {
1666
- targets.push_back({peer.socket, peer.peer_id,
1667
- peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
1668
- }
1669
- }
1670
- }
1671
-
1672
- int sent_count = 0;
1673
- for (const auto& t : targets) {
1674
- if (send_binary_to_peer_unlocked(t.socket, data, message_type, t.send_cipher, t.peer_id)) {
1675
- sent_count++;
1676
- }
1677
- }
1678
- return sent_count;
1679
- }
1680
-
1681
- int RatsClient::broadcast_string_to_peers(const std::string& data) {
1682
- // Convert string to binary and use primary binary method with STRING type
1683
- std::vector<uint8_t> binary_data(data.begin(), data.end());
1684
- return broadcast_binary_to_peers(binary_data, MessageDataType::STRING);
1685
- }
1686
-
1687
- // =========================================================================
1688
- // Peer Information and Management
1689
- // =========================================================================
1690
-
1691
- std::string RatsClient::get_our_peer_id() const {
1692
- return our_peer_id_;
1693
- }
1694
-
1695
- int RatsClient::get_peer_count_unlocked() const {
1696
- // Returns the cached validated peer count (O(1) instead of O(N) scan)
1697
- return validated_peer_count_.load(std::memory_order_relaxed);
1698
- }
1699
-
1700
- int RatsClient::get_peer_count() const {
1701
- return validated_peer_count_.load(std::memory_order_relaxed);
1702
- }
1703
-
1704
- std::string RatsClient::get_peer_id(socket_t socket) const {
1705
- std::lock_guard<std::mutex> lock(peers_mutex_);
1706
- auto peer_it = find_peer_by_socket_unlocked(socket);
1707
- return (peer_it != peers_.end()) ? peer_it->second.peer_id : "";
1708
- }
1709
-
1710
- socket_t RatsClient::get_peer_socket_by_id(const std::string& peer_id) const {
1711
- // Atomic operation - lock once and return copy to avoid race condition
1712
- std::lock_guard<std::mutex> lock(peers_mutex_);
1713
- auto it = peers_.find(peer_id);
1714
- if (it != peers_.end()) {
1715
- return it->second.socket;
1716
- }
1717
- return INVALID_SOCKET_VALUE;
1718
- }
1719
-
1720
- std::vector<RatsPeer> RatsClient::get_all_peers() const {
1721
- std::lock_guard<std::mutex> lock(peers_mutex_);
1722
- std::vector<RatsPeer> result;
1723
- result.reserve(peers_.size());
1724
-
1725
- for (const auto& pair : peers_) {
1726
- result.push_back(pair.second);
1727
- }
1728
-
1729
- return result;
1730
- }
1731
-
1732
- std::vector<RatsPeer> RatsClient::get_validated_peers() const {
1733
- std::lock_guard<std::mutex> lock(peers_mutex_);
1734
- std::vector<RatsPeer> result;
1735
-
1736
- for (const auto& pair : peers_) {
1737
- if (pair.second.is_handshake_completed()) {
1738
- result.push_back(pair.second);
1739
- }
1740
- }
1741
-
1742
- return result;
1743
- }
1744
-
1745
- std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::string& exclude_peer_id) const {
1746
- std::lock_guard<std::mutex> lock(peers_mutex_);
1747
-
1748
- std::vector<RatsPeer> all_validated_peers;
1749
-
1750
- // Get all validated peers excluding the specified peer
1751
- for (const auto& pair : peers_) {
1752
- const RatsPeer& peer = pair.second;
1753
- if (peer.is_handshake_completed() && peer.peer_id != exclude_peer_id) {
1754
- all_validated_peers.push_back(peer);
1755
- }
1756
- }
1757
-
1758
- // If we have fewer peers than requested, return all
1759
- if (all_validated_peers.size() <= static_cast<size_t>(max_count)) {
1760
- return all_validated_peers;
1761
- }
1762
-
1763
- // Randomly select peers
1764
- std::vector<RatsPeer> selected_peers;
1765
- std::random_device rd;
1766
- std::mt19937 gen(rd());
1767
-
1768
- // Use random sampling to select peers
1769
- std::sample(all_validated_peers.begin(), all_validated_peers.end(),
1770
- std::back_inserter(selected_peers), max_count, gen);
1771
-
1772
- return selected_peers;
1773
- }
1774
-
1775
- std::optional<RatsPeer> RatsClient::get_peer_by_id(const std::string& peer_id) const {
1776
- std::lock_guard<std::mutex> lock(peers_mutex_);
1777
- auto it = peers_.find(peer_id);
1778
- if (it != peers_.end()) {
1779
- return it->second;
1780
- }
1781
- return std::nullopt;
1782
- }
1783
-
1784
- std::optional<RatsPeer> RatsClient::get_peer_by_socket(socket_t socket) const {
1785
- std::lock_guard<std::mutex> lock(peers_mutex_);
1786
- auto peer_it = find_peer_by_socket_unlocked(socket);
1787
- if (peer_it != peers_.end()) {
1788
- return peer_it->second;
1789
- }
1790
- return std::nullopt;
1791
- }
1792
-
1793
- // Peer limit management methods
1794
- int RatsClient::get_max_peers() const {
1795
- return max_peers_;
1796
- }
1797
-
1798
- void RatsClient::set_max_peers(int max_peers) {
1799
- max_peers_ = max_peers;
1800
- LOG_CLIENT_INFO("Maximum peers set to " << max_peers_);
1801
- }
1802
-
1803
- bool RatsClient::is_peer_limit_reached() const {
1804
- std::lock_guard<std::mutex> lock(peers_mutex_);
1805
- return get_peer_count_unlocked() >= max_peers_;
1806
- }
1807
-
1808
- std::string RatsClient::generate_temporary_peer_id(socket_t socket, const std::string& connection_info) {
1809
- // Generate unique hash ID using timestamp, socket, connection info, and random component
1810
- auto now = std::chrono::high_resolution_clock::now();
1811
- auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
1812
-
1813
- // Create a random component
1814
- std::random_device rd;
1815
- std::mt19937 gen(rd());
1816
- std::uniform_int_distribution<> dis(0, 255);
1817
-
1818
- // Build hash string
1819
- std::ostringstream hash_stream;
1820
- hash_stream << std::hex << timestamp << "_" << socket << "_";
1821
-
1822
- // Add connection info hash
1823
- std::hash<std::string> hasher;
1824
- hash_stream << hasher(connection_info) << "_";
1825
-
1826
- // Add random component
1827
- for (int i = 0; i < 8; ++i) {
1828
- hash_stream << std::setfill('0') << std::setw(2) << dis(gen);
1829
- }
1830
-
1831
- return hash_stream.str();
1832
- }
1833
-
1834
- std::string RatsClient::normalize_peer_address(const std::string& ip, int port) const {
1835
- // Normalize IPv6 addresses and create consistent format
1836
- std::string normalized_ip = ip;
1837
-
1838
- // Remove brackets from IPv6 addresses if present
1839
- if (!normalized_ip.empty() && normalized_ip.front() == '[' && normalized_ip.back() == ']') {
1840
- normalized_ip = normalized_ip.substr(1, normalized_ip.length() - 2);
1841
- }
1842
-
1843
- // Handle localhost variations
1844
- if (normalized_ip == "localhost" || normalized_ip == "::1") {
1845
- normalized_ip = "127.0.0.1";
1846
- }
1847
-
1848
- // For IPv6 addresses, add brackets for consistency
1849
- if (normalized_ip.find(':') != std::string::npos && normalized_ip.find('.') == std::string::npos) {
1850
- // This is likely an IPv6 address (contains colons but no dots)
1851
- return "[" + normalized_ip + "]:" + std::to_string(port);
1852
- }
1853
-
1854
- return normalized_ip + ":" + std::to_string(port);
1855
- }
1856
-
1857
- // =========================================================================
1858
- // Callback Registration
1859
- // =========================================================================
1860
-
1861
- void RatsClient::set_connection_callback(ConnectionCallback callback) {
1862
- connection_callback_ = callback;
1863
- }
1864
-
1865
- void RatsClient::set_binary_data_callback(BinaryDataCallback callback) {
1866
- binary_data_callback_ = callback;
1867
- }
1868
-
1869
- void RatsClient::set_string_data_callback(StringDataCallback callback) {
1870
- string_data_callback_ = callback;
1871
- }
1872
-
1873
- void RatsClient::set_json_data_callback(JsonDataCallback callback) {
1874
- json_data_callback_ = callback;
1875
- }
1876
-
1877
- void RatsClient::set_disconnect_callback(DisconnectCallback callback) {
1878
- disconnect_callback_ = callback;
1879
- }
1880
-
1881
- // =========================================================================
1882
- // Protocol Configuration
1883
- // =========================================================================
1884
-
1885
- void RatsClient::set_protocol_name(const std::string& protocol_name) {
1886
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
1887
- custom_protocol_name_ = protocol_name;
1888
- LOG_CLIENT_INFO("Protocol name set to: " << protocol_name);
1889
- }
1890
-
1891
- void RatsClient::set_protocol_version(const std::string& protocol_version) {
1892
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
1893
- custom_protocol_version_ = protocol_version;
1894
- LOG_CLIENT_INFO("Protocol version set to: " << protocol_version);
1895
- }
1896
-
1897
- std::string RatsClient::get_protocol_name() const {
1898
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
1899
- return custom_protocol_name_;
1900
- }
1901
-
1902
- std::string RatsClient::get_protocol_version() const {
1903
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
1904
- return custom_protocol_version_;
1905
- }
1906
-
1907
- // =========================================================================
1908
- // Message Exchange API
1909
- // =========================================================================
1910
-
1911
- void RatsClient::on(const std::string& message_type, MessageCallback callback) {
1912
- std::lock_guard<std::mutex> lock(message_handlers_mutex_);
1913
- message_handlers_[message_type].emplace_back(callback, false); // false = not once
1914
- LOG_CLIENT_DEBUG("Registered handler for message type: " << message_type);
1915
- }
1916
-
1917
- void RatsClient::once(const std::string& message_type, MessageCallback callback) {
1918
- std::lock_guard<std::mutex> lock(message_handlers_mutex_);
1919
- message_handlers_[message_type].emplace_back(callback, true); // true = once
1920
- LOG_CLIENT_DEBUG("Registered one-time handler for message type: " << message_type);
1921
- }
1922
-
1923
- void RatsClient::off(const std::string& message_type) {
1924
- std::lock_guard<std::mutex> lock(message_handlers_mutex_);
1925
- auto it = message_handlers_.find(message_type);
1926
- if (it != message_handlers_.end()) {
1927
- size_t removed_count = it->second.size();
1928
- message_handlers_.erase(it);
1929
- LOG_CLIENT_DEBUG("Removed " << removed_count << " handlers for message type: " << message_type);
1930
- }
1931
- }
1932
-
1933
- void RatsClient::send(const std::string& message_type, const nlohmann::json& data, SendCallback callback) {
1934
- if (!running_.load()) {
1935
- LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' - client is not running");
1936
- if (callback) {
1937
- callback(false, "Client is not running");
1938
- }
1939
- return;
1940
- }
1941
-
1942
- LOG_CLIENT_DEBUG("Sending broadcast message type '" << message_type << "'");
1943
-
1944
- // Create rats message
1945
- nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
1946
-
1947
- // Broadcast to all validated peers
1948
- int sent_count = broadcast_rats_message(message);
1949
-
1950
- LOG_CLIENT_DEBUG("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
1951
-
1952
- if (callback) {
1953
- if (sent_count > 0) {
1954
- callback(true, "");
1955
- } else {
1956
- LOG_CLIENT_WARN("No peers to send message to");
1957
- callback(false, "No peers to send message to");
1958
- }
1959
- }
1960
- }
1961
-
1962
- void RatsClient::send(const std::string& peer_id, const std::string& message_type, const nlohmann::json& data, SendCallback callback) {
1963
- if (!running_.load()) {
1964
- LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' to peer " << peer_id << " - client is not running");
1965
- if (callback) {
1966
- callback(false, "Client is not running");
1967
- }
1968
- return;
1969
- }
1970
-
1971
- LOG_CLIENT_DEBUG("Sending targeted message type '" << message_type << "' to peer " << peer_id);
1972
-
1973
- // Create rats message
1974
- nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
1975
-
1976
- // Send to specific peer
1977
- socket_t target_socket = INVALID_SOCKET_VALUE;
1978
- bool peer_found = false;
1979
- bool handshake_completed = false;
1980
-
1981
- {
1982
- std::lock_guard<std::mutex> lock(peers_mutex_);
1983
- auto it = peers_.find(peer_id);
1984
- if (it != peers_.end()) {
1985
- peer_found = true;
1986
- handshake_completed = it->second.is_handshake_completed();
1987
- if (handshake_completed) {
1988
- target_socket = it->second.socket;
1989
- }
1990
- }
1991
- }
1992
-
1993
- if (!peer_found) {
1994
- LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' - peer not found: " << peer_id);
1995
- if (callback) {
1996
- callback(false, "Peer not found: " + peer_id);
1997
- }
1998
- return;
1999
- }
2000
-
2001
- if (!handshake_completed) {
2002
- LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' - peer handshake not completed: " << peer_id);
2003
- if (callback) {
2004
- callback(false, "Peer handshake not completed: " + peer_id);
2005
- }
2006
- return;
2007
- }
2008
-
2009
- bool success = send_json_to_peer(target_socket, message);
2010
-
2011
- LOG_CLIENT_DEBUG("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
2012
-
2013
- if (callback) {
2014
- if (success) {
2015
- callback(true, "");
2016
- } else {
2017
- callback(false, "Failed to send message to peer: " + peer_id);
2018
- }
2019
- }
2020
- }
2021
-
2022
- // Message exchange system helpers
2023
- void RatsClient::call_message_handlers(const std::string& message_type, const std::string& peer_id, const nlohmann::json& data) {
2024
- std::vector<MessageHandler> handlers_to_call;
2025
-
2026
- // Get handlers to call and remove once handlers atomically
2027
- {
2028
- std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2029
- auto it = message_handlers_.find(message_type);
2030
- if (it == message_handlers_.end()) {
2031
- LOG_CLIENT_DEBUG("No handlers registered for message type '" << message_type << "'");
2032
- return;
2033
- }
2034
-
2035
- handlers_to_call = it->second;
2036
-
2037
- // Remove once handlers using erase-remove idiom
2038
- it->second.erase(
2039
- std::remove_if(it->second.begin(), it->second.end(),
2040
- [](const MessageHandler& h) { return h.is_once; }),
2041
- it->second.end());
2042
- }
2043
-
2044
- LOG_CLIENT_DEBUG("Calling " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
2045
-
2046
- // Call handlers outside of mutex to avoid deadlock
2047
- for (const auto& handler : handlers_to_call) {
2048
- try {
2049
- handler.callback(peer_id, data);
2050
- } catch (const std::exception& e) {
2051
- LOG_CLIENT_ERROR("Exception in message handler for type '" << message_type << "': " << e.what());
2052
- } catch (...) {
2053
- LOG_CLIENT_ERROR("Unknown exception in message handler for type '" << message_type << "'");
2054
- }
2055
- }
2056
- }
2057
-
2058
- // =========================================================================
2059
- // Rats Protocol Message Handling
2060
- // =========================================================================
2061
-
2062
- nlohmann::json RatsClient::create_rats_message(const std::string& type, const nlohmann::json& payload, const std::string& sender_peer_id) {
2063
- nlohmann::json message;
2064
- message["rats_protocol"] = true;
2065
- message["type"] = type;
2066
- message["payload"] = payload;
2067
- message["sender_peer_id"] = sender_peer_id;
2068
- message["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(
2069
- std::chrono::high_resolution_clock::now().time_since_epoch()).count();
2070
-
2071
- return message;
2072
- }
2073
-
2074
- void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_id, const nlohmann::json& message) {
2075
- try {
2076
- std::string message_type = message.value("type", "");
2077
- nlohmann::json payload = message.value("payload", nlohmann::json::object());
2078
- std::string sender_peer_id = message.value("sender_peer_id", "");
2079
-
2080
- LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_id);
2081
-
2082
- // Call registered message handlers for all message types (including custom ones)
2083
- call_message_handlers(message_type, sender_peer_id.empty() ? peer_id : sender_peer_id, payload);
2084
-
2085
- // Handle built-in message types for internal functionality
2086
- if (message_type == "peer") {
2087
- handle_peer_exchange_message(socket, peer_id, payload);
2088
- }
2089
- else if (message_type == "peers_request") {
2090
- handle_peers_request_message(socket, peer_id, payload);
2091
- }
2092
- else if (message_type == "peers_response") {
2093
- handle_peers_response_message(socket, peer_id, payload);
2094
- }
2095
- // Custom message types are now handled by registered handlers above
2096
- // No need for else clause - all message types are valid if they have registered handlers
2097
-
2098
- } catch (const nlohmann::json::exception& e) {
2099
- LOG_CLIENT_ERROR("Failed to handle rats message: " << e.what());
2100
- }
2101
- }
2102
-
2103
- void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
2104
- try {
2105
- std::string exchanged_ip = payload.value("ip", "");
2106
- int exchanged_port = payload.value("port", 0);
2107
- std::string exchanged_peer_id = payload.value("peer_id", "");
2108
-
2109
- if (exchanged_ip.empty() || exchanged_port <= 0 || exchanged_peer_id.empty()) {
2110
- LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_id);
2111
- return;
2112
- }
2113
-
2114
- LOG_CLIENT_INFO("Received peer exchange: " << exchanged_ip << ":" << exchanged_port << " (peer_id: " << exchanged_peer_id << ")");
2115
-
2116
- if (!can_connect_to_peer(exchanged_ip, exchanged_port)) {
2117
- return;
2118
- }
2119
-
2120
- // Try to connect to the exchanged peer (non-blocking)
2121
- add_managed_thread(std::thread([this, exchanged_ip, exchanged_port, exchanged_peer_id]() {
2122
- if (connect_to_peer(exchanged_ip, exchanged_port)) {
2123
- LOG_CLIENT_INFO("Successfully connected to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
2124
- } else {
2125
- LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
2126
- }
2127
- }), "peer-exchange-connect-" + exchanged_peer_id.substr(0, 8));
2128
-
2129
- } catch (const nlohmann::json::exception& e) {
2130
- LOG_CLIENT_ERROR("Failed to handle peer exchange message: " << e.what());
2131
- }
2132
- }
2133
-
2134
- // General broadcasting function
2135
- int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id, bool validated_only) {
2136
- // Serialize JSON once before iterating
2137
- std::string json_string;
2138
- try {
2139
- json_string = message.dump();
2140
- } catch (const nlohmann::json::exception& e) {
2141
- LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
2142
- return 0;
2143
- }
2144
- std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
2145
-
2146
- // Collect targets under lock, then enqueue outside
2147
- std::vector<PeerSendTarget> targets;
2148
- {
2149
- std::lock_guard<std::mutex> lock(peers_mutex_);
2150
- targets.reserve(peers_.size());
2151
- for (const auto& [id, peer] : peers_) {
2152
- if (!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) {
2153
- continue;
2154
- }
2155
- if (validated_only && !peer.is_handshake_completed()) {
2156
- continue;
2157
- }
2158
- targets.push_back({peer.socket, peer.peer_id,
2159
- peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
2160
- }
2161
- }
2162
-
2163
- int sent_count = 0;
2164
- for (const auto& t : targets) {
2165
- if (send_binary_to_peer_unlocked(t.socket, binary_data, MessageDataType::JSON, t.send_cipher, t.peer_id)) {
2166
- sent_count++;
2167
- }
2168
- }
2169
- return sent_count;
2170
- }
2171
-
2172
- // Specific message creation functions
2173
- nlohmann::json RatsClient::create_peer_exchange_message(const RatsPeer& peer) {
2174
- // Create peer exchange payload
2175
- nlohmann::json payload;
2176
- payload["ip"] = peer.ip;
2177
- payload["port"] = peer.port;
2178
- payload["peer_id"] = peer.peer_id;
2179
- payload["connection_type"] = peer.is_outgoing ? "outgoing" : "incoming";
2180
-
2181
- // Create rats message - use OUR peer_id as sender, not the advertised peer's id
2182
- return create_rats_message("peer", payload, get_our_peer_id());
2183
- }
2184
-
2185
- void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
2186
- // Don't broadcast exchange messages for ourselves
2187
- if (new_peer.peer_id.empty()) {
2188
- return;
2189
- }
2190
-
2191
- // Create peer exchange message
2192
- nlohmann::json message = create_peer_exchange_message(new_peer);
2193
-
2194
- // Broadcast to all validated peers except the new peer
2195
- int sent_count = broadcast_rats_message(message, new_peer.peer_id);
2196
-
2197
- LOG_CLIENT_INFO("Broadcasted peer exchange message for " << new_peer.ip << ":" << new_peer.port
2198
- << " to " << sent_count << " peers");
2199
- }
2200
-
2201
- // Peers request/response system implementation
2202
- nlohmann::json RatsClient::create_peers_request_message(const std::string& sender_peer_id) {
2203
- nlohmann::json payload;
2204
- payload["max_peers"] = MAX_PEERS_REQUEST_COUNT;
2205
- payload["requester_info"] = {
2206
- {"listen_port", listen_port_},
2207
- {"peer_count", get_peer_count()}
2208
- };
2209
-
2210
- return create_rats_message("peers_request", payload, sender_peer_id);
2211
- }
2212
-
2213
- nlohmann::json RatsClient::create_peers_response_message(const std::vector<RatsPeer>& peers, const std::string& sender_peer_id) {
2214
- nlohmann::json payload;
2215
- nlohmann::json peers_array = nlohmann::json::array();
2216
-
2217
- for (const auto& peer : peers) {
2218
- peers_array.push_back({
2219
- {"ip", peer.ip},
2220
- {"port", peer.port},
2221
- {"peer_id", peer.peer_id},
2222
- {"connection_type", peer.is_outgoing ? "outgoing" : "incoming"}
2223
- });
2224
- }
2225
-
2226
- payload["peers"] = std::move(peers_array);
2227
- payload["total_peers"] = get_peer_count();
2228
-
2229
- return create_rats_message("peers_response", payload, sender_peer_id);
2230
- }
2231
-
2232
- void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
2233
- try {
2234
- int max_peers = payload.value("max_peers", MAX_PEERS_REQUEST_COUNT);
2235
-
2236
- LOG_CLIENT_INFO("Received peers request from " << peer_id << " for up to " << max_peers << " peers");
2237
-
2238
- // Get random peers excluding the requester
2239
- std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_id);
2240
-
2241
- LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_id);
2242
-
2243
- // Create and send peers response
2244
- nlohmann::json response_message = create_peers_response_message(random_peers, peer_id);
2245
-
2246
- if (!send_json_to_peer(socket, response_message)) {
2247
- LOG_CLIENT_ERROR("Failed to send peers response to " << peer_id);
2248
- } else {
2249
- LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " << peer_id);
2250
- }
2251
-
2252
- } catch (const nlohmann::json::exception& e) {
2253
- LOG_CLIENT_ERROR("Failed to handle peers request message: " << e.what());
2254
- }
2255
- }
2256
-
2257
- void RatsClient::handle_peers_response_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
2258
- try {
2259
- nlohmann::json peers_array = payload.value("peers", nlohmann::json::array());
2260
- int total_peers = payload.value("total_peers", 0);
2261
-
2262
- LOG_CLIENT_INFO("Received peers response from " << peer_id << " with " << peers_array.size()
2263
- << " peers (total: " << total_peers << ")");
2264
-
2265
- // Process each peer in the response
2266
- for (const auto& peer_info : peers_array) {
2267
- std::string resp_ip = peer_info.value("ip", "");
2268
- int resp_port = peer_info.value("port", 0);
2269
- std::string resp_peer_id = peer_info.value("peer_id", "");
2270
-
2271
- if (resp_ip.empty() || resp_port <= 0 || resp_peer_id.empty()) {
2272
- LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_id);
2273
- continue;
2274
- }
2275
-
2276
- LOG_CLIENT_DEBUG("Processing peer from response: " << resp_ip << ":" << resp_port << " (peer_id: " << resp_peer_id << ")");
2277
-
2278
- if (!can_connect_to_peer(resp_ip, resp_port)) {
2279
- continue;
2280
- }
2281
-
2282
- LOG_CLIENT_DEBUG("Attempting to connect to peer from response: " << resp_ip << ":" << resp_port);
2283
- add_managed_thread(std::thread([this, resp_ip, resp_port, resp_peer_id]() {
2284
- if (connect_to_peer(resp_ip, resp_port)) {
2285
- LOG_CLIENT_INFO("Successfully connected to peer from response: " << resp_ip << ":" << resp_port);
2286
- } else {
2287
- LOG_CLIENT_DEBUG("Failed to connect to peer from response: " << resp_ip << ":" << resp_port);
2288
- }
2289
- }), "peer-response-connect-" + resp_peer_id.substr(0, 8));
2290
- }
2291
-
2292
- } catch (const nlohmann::json::exception& e) {
2293
- LOG_CLIENT_ERROR("Failed to handle peers response message: " << e.what());
2294
- }
2295
- }
2296
-
2297
- void RatsClient::send_peers_request(socket_t socket, const std::string& our_peer_id) {
2298
- nlohmann::json request_message = create_peers_request_message(our_peer_id);
2299
-
2300
- if (send_json_to_peer(socket, request_message)) {
2301
- LOG_CLIENT_INFO("Sent peers request to socket " << socket);
2302
- } else {
2303
- LOG_CLIENT_ERROR("Failed to send peers request to socket " << socket);
2304
- }
2305
- }
2306
-
2307
- // =========================================================================
2308
- // Helper Functions
2309
- // =========================================================================
2310
-
2311
- std::unique_ptr<RatsClient> create_rats_client(int listen_port) {
2312
- auto client = std::make_unique<RatsClient>(listen_port, 10); // Default 10 max peers
2313
- if (!client->start()) {
2314
- return nullptr;
2315
- }
2316
- return client;
2317
- }
2318
-
2319
- // Version query functions
2320
- const char* rats_get_library_version_string() {
2321
- return librats::version::STRING;
2322
- }
2323
-
2324
- void rats_get_library_version(int* major, int* minor, int* patch, int* build) {
2325
- if (major) *major = librats::version::MAJOR;
2326
- if (minor) *minor = librats::version::MINOR;
2327
- if (patch) *patch = librats::version::PATCH;
2328
- if (build) *build = librats::version::BUILD;
2329
- }
2330
-
2331
- const char* rats_get_library_git_describe() {
2332
- return librats::version::GIT_DESCRIBE;
2333
- }
2334
-
2335
- uint32_t rats_get_library_abi() {
2336
- // ABI policy: MAJOR bumps on breaking changes; MINOR for additive; PATCH ignored in ABI id
2337
- return (static_cast<uint32_t>(librats::version::MAJOR) << 16) |
2338
- (static_cast<uint32_t>(librats::version::MINOR) << 8) |
2339
- (static_cast<uint32_t>(librats::version::PATCH));
2340
- }
2341
-
2342
- bool RatsClient::parse_address_string(const std::string& address_str, std::string& out_ip, int& out_port) {
2343
- if (address_str.empty()) {
2344
- return false;
2345
- }
2346
-
2347
- size_t colon_pos;
2348
- if (address_str.front() == '[') {
2349
- // IPv6 format: [ip]:port
2350
- size_t bracket_end = address_str.find(']');
2351
- if (bracket_end == std::string::npos || bracket_end < 2) { // Must be at least [a]
2352
- return false;
2353
- }
2354
- out_ip = address_str.substr(1, bracket_end - 1);
2355
- colon_pos = address_str.find(':', bracket_end);
2356
- } else {
2357
- // IPv4 or IPv6 without brackets
2358
- colon_pos = address_str.find_last_of(':');
2359
- if (colon_pos == std::string::npos || colon_pos == 0) {
2360
- return false;
2361
- }
2362
- out_ip = address_str.substr(0, colon_pos);
2363
- }
2364
-
2365
- if (colon_pos == std::string::npos || colon_pos + 1 >= address_str.length()) {
2366
- return false;
2367
- }
2368
-
2369
- try {
2370
- out_port = std::stoi(address_str.substr(colon_pos + 1));
2371
- } catch (const std::exception&) {
2372
- return false;
2373
- }
2374
-
2375
- return !out_ip.empty() && out_port > 0 && out_port <= 65535;
2376
- }
2377
-
2378
- } // namespace librats