librats 1.0.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/README.md +145 -331
  2. package/binding.gyp +16 -3
  3. package/lib/index.d.ts +288 -696
  4. package/lib/index.js +407 -44
  5. package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
  7. package/native-src/CMakeLists.txt +404 -179
  8. package/native-src/LICENSE +1 -1
  9. package/native-src/src/librats/bindings/rats.cpp +762 -0
  10. package/native-src/src/librats/bindings/rats.h +380 -0
  11. package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
  12. package/native-src/src/librats/bittorrent/bencode.h +176 -0
  13. package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
  14. package/native-src/src/librats/bittorrent/bitfield.h +76 -0
  15. package/native-src/src/librats/bittorrent/byte_io.h +58 -0
  16. package/native-src/src/librats/bittorrent/choker.cpp +25 -0
  17. package/native-src/src/librats/bittorrent/choker.h +46 -0
  18. package/native-src/src/librats/bittorrent/client.cpp +413 -0
  19. package/native-src/src/librats/bittorrent/client.h +227 -0
  20. package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
  21. package/native-src/src/librats/bittorrent/disk_io.h +150 -0
  22. package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
  23. package/native-src/src/librats/bittorrent/extensions.h +94 -0
  24. package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
  25. package/native-src/src/librats/bittorrent/file_storage.h +79 -0
  26. package/native-src/src/librats/bittorrent/log.h +42 -0
  27. package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
  28. package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
  29. package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
  30. package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
  31. package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
  32. package/native-src/src/librats/bittorrent/peer_list.h +75 -0
  33. package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
  34. package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
  35. package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
  36. package/native-src/src/librats/bittorrent/reactor.h +89 -0
  37. package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
  38. package/native-src/src/librats/bittorrent/resume_data.h +41 -0
  39. package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
  40. package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
  41. package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
  42. package/native-src/src/librats/bittorrent/torrent.h +260 -0
  43. package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
  44. package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
  45. package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
  46. package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
  47. package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
  48. package/native-src/src/librats/bittorrent/tracker.h +108 -0
  49. package/native-src/src/librats/bittorrent/types.cpp +206 -0
  50. package/native-src/src/librats/bittorrent/types.h +86 -0
  51. package/native-src/src/librats/core/address.cpp +35 -0
  52. package/native-src/src/librats/core/address.h +78 -0
  53. package/native-src/src/librats/core/bytes.h +69 -0
  54. package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
  55. package/native-src/src/librats/core/chained_send_buffer.h +183 -0
  56. package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
  57. package/native-src/src/librats/core/endpoint_parse.h +31 -0
  58. package/native-src/src/librats/core/event_bus.h +70 -0
  59. package/native-src/src/librats/core/host_endpoint.h +56 -0
  60. package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
  61. package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
  62. package/native-src/src/librats/core/ip_address.cpp +120 -0
  63. package/native-src/src/librats/core/ip_address.h +109 -0
  64. package/native-src/src/librats/core/mpsc_queue.h +47 -0
  65. package/native-src/src/librats/core/notifier.h +74 -0
  66. package/native-src/src/librats/core/receive_buffer.cpp +219 -0
  67. package/native-src/src/librats/core/receive_buffer.h +171 -0
  68. package/native-src/src/librats/core/service_registry.h +58 -0
  69. package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
  70. package/native-src/src/librats/core/socket.h +496 -0
  71. package/native-src/src/librats/core/timer_queue.h +105 -0
  72. package/native-src/src/librats/core/types.cpp +43 -0
  73. package/native-src/src/librats/core/types.h +103 -0
  74. package/native-src/src/librats/core/wakeup_pipe.h +83 -0
  75. package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
  76. package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
  77. package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
  78. package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
  79. package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
  80. package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
  81. package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
  82. package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
  83. package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
  84. package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
  85. package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
  86. package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
  87. package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
  88. package/native-src/src/librats/crypto/hkdf.c +266 -0
  89. package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
  90. package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
  91. package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
  92. package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
  93. package/native-src/src/librats/crypto/poly1305.h +37 -0
  94. package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
  95. package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
  96. package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
  97. package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
  98. package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
  99. package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
  100. package/native-src/src/librats/dht/announce.cpp +37 -0
  101. package/native-src/src/librats/dht/announce.h +41 -0
  102. package/native-src/src/librats/dht/bep42.cpp +109 -0
  103. package/native-src/src/librats/dht/bep42.h +48 -0
  104. package/native-src/src/librats/dht/dht.cpp +501 -0
  105. package/native-src/src/librats/dht/dht.h +119 -0
  106. package/native-src/src/librats/dht/dht_runner.cpp +103 -0
  107. package/native-src/src/librats/dht/dht_runner.h +71 -0
  108. package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
  109. package/native-src/src/librats/dht/dos_blocker.h +47 -0
  110. package/native-src/src/librats/dht/find_peers.cpp +52 -0
  111. package/native-src/src/librats/dht/find_peers.h +73 -0
  112. package/native-src/src/librats/dht/id.h +167 -0
  113. package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
  114. package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
  115. package/native-src/src/librats/dht/log.h +38 -0
  116. package/native-src/src/librats/dht/node.cpp +473 -0
  117. package/native-src/src/librats/dht/node.h +164 -0
  118. package/native-src/src/librats/dht/node_entry.h +81 -0
  119. package/native-src/src/librats/dht/observer.h +72 -0
  120. package/native-src/src/librats/dht/persistence.cpp +90 -0
  121. package/native-src/src/librats/dht/persistence.h +32 -0
  122. package/native-src/src/librats/dht/routing_table.cpp +559 -0
  123. package/native-src/src/librats/dht/routing_table.h +185 -0
  124. package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
  125. package/native-src/src/librats/dht/rpc_manager.h +77 -0
  126. package/native-src/src/librats/dht/storage.cpp +92 -0
  127. package/native-src/src/librats/dht/storage.h +74 -0
  128. package/native-src/src/librats/dht/transport.h +27 -0
  129. package/native-src/src/librats/dht/traversal.cpp +326 -0
  130. package/native-src/src/librats/dht/traversal.h +120 -0
  131. package/native-src/src/librats/dht/udp_transport.cpp +49 -0
  132. package/native-src/src/librats/dht/udp_transport.h +51 -0
  133. package/native-src/src/librats/mdns/log.h +22 -0
  134. package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
  135. package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
  136. package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
  137. package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
  138. package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
  139. package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
  140. package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
  141. package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
  142. package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
  143. package/native-src/src/librats/node/circuit_service.h +84 -0
  144. package/native-src/src/librats/node/config.h +110 -0
  145. package/native-src/src/librats/node/dial_service.h +54 -0
  146. package/native-src/src/librats/node/dialer.cpp +264 -0
  147. package/native-src/src/librats/node/dialer.h +188 -0
  148. package/native-src/src/librats/node/host_events.h +26 -0
  149. package/native-src/src/librats/node/identify.cpp +130 -0
  150. package/native-src/src/librats/node/identify.h +71 -0
  151. package/native-src/src/librats/node/nat_status.cpp +103 -0
  152. package/native-src/src/librats/node/nat_status.h +118 -0
  153. package/native-src/src/librats/node/node.cpp +865 -0
  154. package/native-src/src/librats/node/node.h +344 -0
  155. package/native-src/src/librats/node/node_context.h +33 -0
  156. package/native-src/src/librats/node/peer_network.h +91 -0
  157. package/native-src/src/librats/peer/peer.h +49 -0
  158. package/native-src/src/librats/peer/peer_book.cpp +181 -0
  159. package/native-src/src/librats/peer/peer_book.h +88 -0
  160. package/native-src/src/librats/peer/peer_id.cpp +72 -0
  161. package/native-src/src/librats/peer/peer_id.h +62 -0
  162. package/native-src/src/librats/peer/peer_info.h +37 -0
  163. package/native-src/src/librats/peer/peer_table.cpp +170 -0
  164. package/native-src/src/librats/peer/peer_table.h +148 -0
  165. package/native-src/src/librats/security/handshaker.h +66 -0
  166. package/native-src/src/librats/security/identity.h +43 -0
  167. package/native-src/src/librats/security/noise_security.cpp +122 -0
  168. package/native-src/src/librats/security/noise_security.h +37 -0
  169. package/native-src/src/librats/security/plaintext_security.h +106 -0
  170. package/native-src/src/librats/security/session.h +37 -0
  171. package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
  172. package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
  173. package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
  174. package/native-src/src/librats/subsystems/bittorrent.h +136 -0
  175. package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
  176. package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
  177. package/native-src/src/librats/subsystems/dht_service.h +36 -0
  178. package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
  179. package/native-src/src/librats/subsystems/file_transfer.h +367 -0
  180. package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
  181. package/native-src/src/librats/subsystems/hole_punch.h +290 -0
  182. package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
  183. package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
  184. package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
  185. package/native-src/src/librats/subsystems/message_json.cpp +112 -0
  186. package/native-src/src/librats/subsystems/message_json.h +88 -0
  187. package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
  188. package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
  189. package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
  190. package/native-src/src/librats/subsystems/ping_service.h +66 -0
  191. package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
  192. package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
  193. package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
  194. package/native-src/src/librats/subsystems/pubsub.h +175 -0
  195. package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
  196. package/native-src/src/librats/subsystems/reconnection.h +126 -0
  197. package/native-src/src/librats/subsystems/relay.cpp +1142 -0
  198. package/native-src/src/librats/subsystems/relay.h +211 -0
  199. package/native-src/src/librats/subsystems/relay_service.h +46 -0
  200. package/native-src/src/librats/transport/connection.cpp +343 -0
  201. package/native-src/src/librats/transport/connection.h +283 -0
  202. package/native-src/src/librats/transport/link.h +96 -0
  203. package/native-src/src/librats/transport/reactor.cpp +588 -0
  204. package/native-src/src/librats/transport/reactor.h +262 -0
  205. package/native-src/src/librats/transport/reactor_pool.h +81 -0
  206. package/native-src/src/librats/transport/relay_link.cpp +208 -0
  207. package/native-src/src/librats/transport/relay_link.h +303 -0
  208. package/native-src/src/librats/transport/tcp_link.cpp +49 -0
  209. package/native-src/src/librats/transport/tcp_link.h +43 -0
  210. package/native-src/src/librats/transport/udp_mux.cpp +617 -0
  211. package/native-src/src/librats/transport/udp_mux.h +363 -0
  212. package/native-src/src/librats/transport/udp_packet.cpp +121 -0
  213. package/native-src/src/librats/transport/udp_packet.h +190 -0
  214. package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
  215. package/native-src/src/librats/transport/udp_stream.h +614 -0
  216. package/native-src/src/librats/util/features.h.in +51 -0
  217. package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
  218. package/native-src/src/librats/util/fs.h +136 -0
  219. package/native-src/src/librats/util/json.cpp +1002 -0
  220. package/native-src/src/librats/util/json.h +444 -0
  221. package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
  222. package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
  223. package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
  224. package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
  225. package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
  226. package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
  227. package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
  228. package/native-src/src/librats/util/rats_export.h +69 -0
  229. package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
  230. package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
  231. package/native-src/src/librats/wire/frame.cpp +76 -0
  232. package/native-src/src/librats/wire/frame.h +111 -0
  233. package/native-src/src/librats/wire/message_router.cpp +45 -0
  234. package/native-src/src/librats/wire/message_router.h +47 -0
  235. package/package.json +5 -4
  236. package/scripts/build-librats.js +1 -0
  237. package/scripts/postinstall.js +3 -3
  238. package/scripts/prepare-package.js +4 -4
  239. package/scripts/verify-installation.js +63 -105
  240. package/src/librats_node.cpp +1067 -1323
  241. package/native-src/src/bencode.cpp +0 -485
  242. package/native-src/src/bencode.h +0 -145
  243. package/native-src/src/bittorrent.cpp +0 -14
  244. package/native-src/src/bittorrent.h +0 -74
  245. package/native-src/src/bt_bitfield.cpp +0 -372
  246. package/native-src/src/bt_bitfield.h +0 -316
  247. package/native-src/src/bt_choker.cpp +0 -228
  248. package/native-src/src/bt_choker.h +0 -147
  249. package/native-src/src/bt_client.cpp +0 -1047
  250. package/native-src/src/bt_client.h +0 -445
  251. package/native-src/src/bt_create_torrent.cpp +0 -677
  252. package/native-src/src/bt_create_torrent.h +0 -473
  253. package/native-src/src/bt_extension.cpp +0 -469
  254. package/native-src/src/bt_extension.h +0 -309
  255. package/native-src/src/bt_file_storage.cpp +0 -261
  256. package/native-src/src/bt_file_storage.h +0 -298
  257. package/native-src/src/bt_handshake.cpp +0 -134
  258. package/native-src/src/bt_handshake.h +0 -157
  259. package/native-src/src/bt_messages.cpp +0 -364
  260. package/native-src/src/bt_messages.h +0 -324
  261. package/native-src/src/bt_network.cpp +0 -1007
  262. package/native-src/src/bt_network.h +0 -417
  263. package/native-src/src/bt_peer_connection.cpp +0 -742
  264. package/native-src/src/bt_peer_connection.h +0 -592
  265. package/native-src/src/bt_piece_picker.cpp +0 -786
  266. package/native-src/src/bt_piece_picker.h +0 -473
  267. package/native-src/src/bt_resume_data.cpp +0 -410
  268. package/native-src/src/bt_resume_data.h +0 -249
  269. package/native-src/src/bt_torrent.cpp +0 -2120
  270. package/native-src/src/bt_torrent.h +0 -641
  271. package/native-src/src/bt_torrent_info.cpp +0 -659
  272. package/native-src/src/bt_torrent_info.h +0 -418
  273. package/native-src/src/bt_types.h +0 -621
  274. package/native-src/src/chained_send_buffer.cpp +0 -75
  275. package/native-src/src/chained_send_buffer.h +0 -137
  276. package/native-src/src/crypto/hkdf.c +0 -266
  277. package/native-src/src/crypto/poly1305.h +0 -36
  278. package/native-src/src/dht.cpp +0 -3311
  279. package/native-src/src/dht.h +0 -717
  280. package/native-src/src/disk_io.cpp +0 -632
  281. package/native-src/src/disk_io.h +0 -315
  282. package/native-src/src/file_transfer.cpp +0 -1415
  283. package/native-src/src/file_transfer.h +0 -286
  284. package/native-src/src/fs.h +0 -108
  285. package/native-src/src/gossipsub.cpp +0 -1139
  286. package/native-src/src/gossipsub.h +0 -403
  287. package/native-src/src/ice.cpp +0 -893
  288. package/native-src/src/ice.h +0 -559
  289. package/native-src/src/json.hpp +0 -25526
  290. package/native-src/src/librats.cpp +0 -2378
  291. package/native-src/src/librats.h +0 -2324
  292. package/native-src/src/librats_bittorrent.cpp +0 -601
  293. package/native-src/src/librats_c.cpp +0 -1557
  294. package/native-src/src/librats_c.h +0 -323
  295. package/native-src/src/librats_discovery.cpp +0 -402
  296. package/native-src/src/librats_encryption.cpp +0 -275
  297. package/native-src/src/librats_file_transfer.cpp +0 -144
  298. package/native-src/src/librats_gossipsub.cpp +0 -289
  299. package/native-src/src/librats_ice.cpp +0 -213
  300. package/native-src/src/librats_log_macros.h +0 -36
  301. package/native-src/src/librats_logging.cpp +0 -173
  302. package/native-src/src/librats_mdns.cpp +0 -166
  303. package/native-src/src/librats_persistence.cpp +0 -796
  304. package/native-src/src/librats_portmap.cpp +0 -419
  305. package/native-src/src/librats_reconnection.cpp +0 -218
  306. package/native-src/src/librats_statistic.cpp +0 -105
  307. package/native-src/src/librats_storage.cpp +0 -189
  308. package/native-src/src/rats_export.h +0 -17
  309. package/native-src/src/receive_buffer.cpp +0 -82
  310. package/native-src/src/receive_buffer.h +0 -127
  311. package/native-src/src/socket.h +0 -228
  312. package/native-src/src/threadmanager.cpp +0 -105
  313. package/native-src/src/threadmanager.h +0 -53
  314. package/native-src/src/tracker.cpp +0 -1264
  315. package/native-src/src/tracker.h +0 -319
  316. package/native-src/src/turn.cpp +0 -762
  317. package/native-src/src/turn.h +0 -460
  318. package/native-src/src/wakeup_pipe.h +0 -60
  319. /package/native-src/src/{os.h → librats/util/os.h} +0 -0
@@ -0,0 +1,1002 @@
1
+ #include "librats/util/json.h"
2
+
3
+ #include <cerrno>
4
+ #include <charconv>
5
+ #include <cmath>
6
+ #include <cstdio>
7
+ #include <cstdlib>
8
+ #include <cstring>
9
+ #include <functional>
10
+ #include <istream>
11
+ #include <iterator>
12
+ #include <ostream>
13
+
14
+ // std::to_chars / std::from_chars for floating-point is a C++17 feature, but
15
+ // some standard libraries shipped it later than the integer overloads. The
16
+ // feature-test macro is defined (to 201611L) only once full support — including
17
+ // the floating-point overloads — is present (libstdc++ ≥ 11, MSVC STL ≥ 19.24,
18
+ // libc++ once complete). When it is absent we fall back to the classic
19
+ // snprintf / strtod path, which is slower but produces identical results.
20
+ #if defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201611L
21
+ # define LIBRATS_JSON_CHARCONV 1
22
+ #else
23
+ # define LIBRATS_JSON_CHARCONV 0
24
+ #endif
25
+
26
+ // Caveat: libc++ defines __cpp_lib_to_chars (it has the floating-point to_chars
27
+ // overloads) yet still ships floating-point from_chars as = delete (true as of
28
+ // Xcode 16.4 / current libc++). So the macro above does NOT imply from_chars
29
+ // works for double — gate the float-parsing path on its own predicate and fall
30
+ // back to strtod where the overload is missing.
31
+ #if LIBRATS_JSON_CHARCONV && !defined(_LIBCPP_VERSION)
32
+ # define LIBRATS_JSON_FROM_CHARS_FLOAT 1
33
+ #else
34
+ # define LIBRATS_JSON_FROM_CHARS_FLOAT 0
35
+ #endif
36
+
37
+ namespace librats {
38
+
39
+ // ── Object ──────────────────────────────────────────────────────────────────
40
+ //
41
+ // The index is a power-of-two open-addressing table of 1-based positions into
42
+ // items_ (a slot value of 0 means empty). It owns no keys: every comparison
43
+ // reads the key back from items_, so a key exists exactly once — in items_.
44
+ // An erase shifts positions, so it rebuilds the table; between rebuilds the
45
+ // table only grows, which means linear probing needs no tombstones.
46
+
47
+ std::size_t Json::Object::hash_key(const std::string& key) {
48
+ return std::hash<std::string>{}(key);
49
+ }
50
+
51
+ void Json::Object::rebuild_index() {
52
+ // Smallest power-of-two capacity that keeps the load factor below 0.5 once
53
+ // the key about to be inserted is in (hence the +1), so probe chains stay
54
+ // short. Slots are uint32_t, so even at this headroom the whole index is a
55
+ // few KB for thousands of keys — negligible beside the keys and values.
56
+ std::size_t cap = 32;
57
+ while (cap < (items_.size() + 1) * 2) cap <<= 1;
58
+ index_.assign(cap, 0);
59
+ const std::size_t mask = cap - 1;
60
+ for (std::size_t i = 0; i < items_.size(); ++i) {
61
+ std::size_t slot = hash_key(items_[i].first) & mask;
62
+ while (index_[slot]) slot = (slot + 1) & mask;
63
+ index_[slot] = static_cast<std::uint32_t>(i + 1);
64
+ }
65
+ }
66
+
67
+ void Json::Object::reindex() {
68
+ // Called after an erase shifts positions: rebuild while large, else drop the
69
+ // index and fall back to linear scans.
70
+ if (items_.size() > kIndexThreshold) rebuild_index();
71
+ else index_ = {};
72
+ }
73
+
74
+ template <typename K>
75
+ Json& Json::Object::emplace_key(K&& key) {
76
+ if (indexed()) {
77
+ // Grow first if seating one more key would crowd the table; growing
78
+ // rehashes, so it must happen before we compute a slot below.
79
+ if ((items_.size() + 1) * 2 > index_.size()) rebuild_index();
80
+
81
+ // A single probe both finds an existing key and locates the empty slot
82
+ // a new one would take.
83
+ const std::size_t mask = index_.size() - 1;
84
+ std::size_t slot = hash_key(key) & mask;
85
+ while (std::uint32_t e = index_[slot]) {
86
+ if (items_[e - 1].first == key) return items_[e - 1].second;
87
+ slot = (slot + 1) & mask;
88
+ }
89
+ items_.emplace_back(std::forward<K>(key), Json());
90
+ index_[slot] = static_cast<std::uint32_t>(items_.size()); // 1-based position
91
+ return items_.back().second;
92
+ }
93
+
94
+ // Small object: linear scan, no index, no allocation.
95
+ for (auto& kv : items_)
96
+ if (kv.first == key) return kv.second;
97
+ items_.emplace_back(std::forward<K>(key), Json());
98
+ if (items_.size() > kIndexThreshold) rebuild_index();
99
+ return items_.back().second;
100
+ }
101
+
102
+ Json& Json::Object::operator[](const std::string& key) { return emplace_key(key); }
103
+ Json& Json::Object::operator[](std::string&& key) { return emplace_key(std::move(key)); }
104
+
105
+ const Json* Json::Object::find(const std::string& key) const {
106
+ if (indexed()) {
107
+ const std::size_t mask = index_.size() - 1;
108
+ std::size_t slot = hash_key(key) & mask;
109
+ while (std::uint32_t e = index_[slot]) {
110
+ if (items_[e - 1].first == key) return &items_[e - 1].second;
111
+ slot = (slot + 1) & mask;
112
+ }
113
+ return nullptr;
114
+ }
115
+ for (const auto& kv : items_)
116
+ if (kv.first == key) return &kv.second;
117
+ return nullptr;
118
+ }
119
+
120
+ Json* Json::Object::find(const std::string& key) {
121
+ const Object* self = this;
122
+ return const_cast<Json*>(self->find(key));
123
+ }
124
+
125
+ bool Json::Object::erase(const std::string& key) {
126
+ std::size_t pos = items_.size();
127
+ if (indexed()) {
128
+ const std::size_t mask = index_.size() - 1;
129
+ std::size_t slot = hash_key(key) & mask;
130
+ while (std::uint32_t e = index_[slot]) {
131
+ if (items_[e - 1].first == key) { pos = e - 1; break; }
132
+ slot = (slot + 1) & mask;
133
+ }
134
+ } else {
135
+ for (std::size_t i = 0; i < items_.size(); ++i)
136
+ if (items_[i].first == key) { pos = i; break; }
137
+ }
138
+ if (pos == items_.size()) return false;
139
+ items_.erase(items_.begin() + static_cast<std::ptrdiff_t>(pos));
140
+ reindex();
141
+ return true;
142
+ }
143
+
144
+ bool Json::Object::operator==(const Object& other) const {
145
+ if (items_.size() != other.items_.size()) return false;
146
+ for (const auto& kv : items_) {
147
+ const Json* o = other.find(kv.first);
148
+ if (!o || !(*o == kv.second)) return false;
149
+ }
150
+ return true;
151
+ }
152
+
153
+ // ── Lifetime ────────────────────────────────────────────────────────────────
154
+
155
+ void Json::destroy() noexcept {
156
+ switch (type_) {
157
+ case Type::String: delete str_; break;
158
+ case Type::Array: delete arr_; break;
159
+ case Type::Object: delete obj_; break;
160
+ default: break;
161
+ }
162
+ }
163
+
164
+ void Json::copy_from(const Json& o) {
165
+ // Commit type_ only after the (possibly throwing) allocation succeeds: if
166
+ // `new` throws, this value keeps its prior type_/payload untouched, so a
167
+ // caller that started from a valid state (Null in the ctor / after the reset
168
+ // in operator=) stays valid and its destructor never frees a stale pointer.
169
+ switch (o.type_) {
170
+ case Type::Boolean: bool_ = o.bool_; break;
171
+ case Type::Integer: int_ = o.int_; break;
172
+ case Type::Unsigned: uint_ = o.uint_; break;
173
+ case Type::Float: float_ = o.float_; break;
174
+ case Type::String: { auto* p = new std::string(*o.str_); str_ = p; } break;
175
+ case Type::Array: { auto* p = new Array(*o.arr_); arr_ = p; } break;
176
+ case Type::Object: { auto* p = new Object(*o.obj_); obj_ = p; } break;
177
+ default: break; // Null / Discarded carry no payload
178
+ }
179
+ type_ = o.type_;
180
+ }
181
+
182
+ void Json::move_from(Json& o) noexcept {
183
+ type_ = o.type_;
184
+ switch (o.type_) {
185
+ case Type::Boolean: bool_ = o.bool_; break;
186
+ case Type::Integer: int_ = o.int_; break;
187
+ case Type::Unsigned: uint_ = o.uint_; break;
188
+ case Type::Float: float_ = o.float_; break;
189
+ case Type::String: str_ = o.str_; break;
190
+ case Type::Array: arr_ = o.arr_; break;
191
+ case Type::Object: obj_ = o.obj_; break;
192
+ default: break;
193
+ }
194
+ o.type_ = Type::Null; // ownership transferred; leave source a valid null
195
+ }
196
+
197
+ Json& Json::operator=(const Json& o) {
198
+ if (this == &o) return *this;
199
+ destroy();
200
+ // Reset to a valid Null first: destroy() freed the old payload but left the
201
+ // old type_/pointer in place, so if copy_from below throws, the destructor
202
+ // would otherwise free a dangling pointer. As Null, a failed copy is safe.
203
+ type_ = Type::Null;
204
+ copy_from(o);
205
+ return *this;
206
+ }
207
+
208
+ Json& Json::operator=(Json&& o) noexcept {
209
+ if (this == &o) return *this;
210
+ destroy();
211
+ move_from(o);
212
+ return *this;
213
+ }
214
+
215
+ // ── initializer_list construction ───────────────────────────────────────────
216
+
217
+ Json::Json(std::initializer_list<Json> init) {
218
+ // nlohmann's heuristic: a non-empty list whose every element is a
219
+ // two-element array starting with a string is an object; else an array.
220
+ bool looks_like_object = init.size() > 0;
221
+ for (const Json& el : init) {
222
+ if (!(el.is_array() && el.size() == 2 && el.as_array()[0].is_string())) {
223
+ looks_like_object = false;
224
+ break;
225
+ }
226
+ }
227
+
228
+ if (looks_like_object) {
229
+ type_ = Type::Object;
230
+ obj_ = new Object();
231
+ for (const Json& el : init) {
232
+ const Array& pair = el.as_array();
233
+ (*obj_)[pair[0].as_string()] = pair[1];
234
+ }
235
+ } else {
236
+ type_ = Type::Array;
237
+ arr_ = new Array(init.begin(), init.end());
238
+ }
239
+ }
240
+
241
+ // ── Scalar extraction ───────────────────────────────────────────────────────
242
+
243
+ bool Json::as_bool() const {
244
+ switch (type_) {
245
+ case Type::Boolean: return bool_;
246
+ case Type::Integer: return int_ != 0;
247
+ case Type::Unsigned: return uint_ != 0;
248
+ case Type::Float: return float_ != 0.0;
249
+ default: throw JsonError("Json: value is not convertible to bool");
250
+ }
251
+ }
252
+
253
+ int64_t Json::as_int64() const {
254
+ switch (type_) {
255
+ case Type::Integer: return int_;
256
+ case Type::Unsigned: return static_cast<int64_t>(uint_);
257
+ case Type::Float: return static_cast<int64_t>(float_);
258
+ case Type::Boolean: return bool_ ? 1 : 0;
259
+ default: throw JsonError("Json: value is not a number");
260
+ }
261
+ }
262
+
263
+ uint64_t Json::as_uint64() const {
264
+ switch (type_) {
265
+ case Type::Integer: return static_cast<uint64_t>(int_);
266
+ case Type::Unsigned: return uint_;
267
+ case Type::Float: return static_cast<uint64_t>(float_);
268
+ case Type::Boolean: return bool_ ? 1u : 0u;
269
+ default: throw JsonError("Json: value is not a number");
270
+ }
271
+ }
272
+
273
+ double Json::as_double() const {
274
+ switch (type_) {
275
+ case Type::Integer: return static_cast<double>(int_);
276
+ case Type::Unsigned: return static_cast<double>(uint_);
277
+ case Type::Float: return float_;
278
+ case Type::Boolean: return bool_ ? 1.0 : 0.0;
279
+ default: throw JsonError("Json: value is not a number");
280
+ }
281
+ }
282
+
283
+ const std::string& Json::as_string() const {
284
+ if (type_ != Type::String) throw JsonError("Json: value is not a string");
285
+ return *str_;
286
+ }
287
+
288
+ // ── Size / emptiness ────────────────────────────────────────────────────────
289
+
290
+ std::size_t Json::size() const noexcept {
291
+ switch (type_) {
292
+ case Type::Null:
293
+ case Type::Discarded: return 0;
294
+ case Type::Array: return arr_->size();
295
+ case Type::Object: return obj_->size();
296
+ default: return 1; // a scalar counts as one element
297
+ }
298
+ }
299
+
300
+ bool Json::empty() const noexcept {
301
+ switch (type_) {
302
+ case Type::Null: return true;
303
+ case Type::Array: return arr_->empty();
304
+ case Type::Object: return obj_->empty();
305
+ default: return false;
306
+ }
307
+ }
308
+
309
+ // ── Object / array access ───────────────────────────────────────────────────
310
+
311
+ Json& Json::operator[](const std::string& key) {
312
+ if (is_null()) { type_ = Type::Object; obj_ = new Object(); }
313
+ if (!is_object()) throw JsonError("Json: operator[](key) on a non-object value");
314
+ return (*obj_)[key];
315
+ }
316
+
317
+ const Json& Json::operator[](const std::string& key) const {
318
+ if (!is_object()) throw JsonError("Json: operator[](key) on a non-object value");
319
+ const Json* v = obj_->find(key);
320
+ if (!v) throw JsonError("Json: key not found: " + key);
321
+ return *v;
322
+ }
323
+
324
+ Json& Json::operator[](std::size_t index) {
325
+ if (is_null()) { type_ = Type::Array; arr_ = new Array(); }
326
+ if (!is_array()) throw JsonError("Json: operator[](index) on a non-array value");
327
+ if (index >= arr_->size()) arr_->resize(index + 1);
328
+ return (*arr_)[index];
329
+ }
330
+
331
+ const Json& Json::operator[](std::size_t index) const {
332
+ if (!is_array()) throw JsonError("Json: operator[](index) on a non-array value");
333
+ if (index >= arr_->size()) throw JsonError("Json: array index out of range");
334
+ return (*arr_)[index];
335
+ }
336
+
337
+ Json& Json::at(const std::string& key) {
338
+ if (!is_object()) throw JsonError("Json: at(key) on a non-object value");
339
+ Json* v = obj_->find(key);
340
+ if (!v) throw JsonError("Json: key not found: " + key);
341
+ return *v;
342
+ }
343
+
344
+ const Json& Json::at(const std::string& key) const {
345
+ if (!is_object()) throw JsonError("Json: at(key) on a non-object value");
346
+ const Json* v = obj_->find(key);
347
+ if (!v) throw JsonError("Json: key not found: " + key);
348
+ return *v;
349
+ }
350
+
351
+ Json& Json::at(std::size_t index) {
352
+ if (!is_array()) throw JsonError("Json: at(index) on a non-array value");
353
+ if (index >= arr_->size()) throw JsonError("Json: array index out of range");
354
+ return (*arr_)[index];
355
+ }
356
+
357
+ const Json& Json::at(std::size_t index) const {
358
+ if (!is_array()) throw JsonError("Json: at(index) on a non-array value");
359
+ if (index >= arr_->size()) throw JsonError("Json: array index out of range");
360
+ return (*arr_)[index];
361
+ }
362
+
363
+ bool Json::erase(const std::string& key) {
364
+ if (!is_object()) return false;
365
+ return obj_->erase(key);
366
+ }
367
+
368
+ void Json::erase(std::size_t index) {
369
+ if (!is_array()) throw JsonError("Json: erase(index) on a non-array value");
370
+ if (index >= arr_->size()) throw JsonError("Json: array index out of range");
371
+ arr_->erase(arr_->begin() + static_cast<std::ptrdiff_t>(index));
372
+ }
373
+
374
+ Json& Json::front() {
375
+ if (is_array()) { if (arr_->empty()) throw JsonError("Json: front() on empty array"); return arr_->front(); }
376
+ if (is_object()) { if (obj_->empty()) throw JsonError("Json: front() on empty object"); return obj_->begin()->second; }
377
+ throw JsonError("Json: front() on a non-container value");
378
+ }
379
+
380
+ const Json& Json::front() const {
381
+ if (is_array()) { if (arr_->empty()) throw JsonError("Json: front() on empty array"); return arr_->front(); }
382
+ if (is_object()) { if (obj_->empty()) throw JsonError("Json: front() on empty object"); return obj_->begin()->second; }
383
+ throw JsonError("Json: front() on a non-container value");
384
+ }
385
+
386
+ Json& Json::back() {
387
+ if (is_array()) { if (arr_->empty()) throw JsonError("Json: back() on empty array"); return arr_->back(); }
388
+ if (is_object()) { if (obj_->empty()) throw JsonError("Json: back() on empty object"); return (obj_->end() - 1)->second; }
389
+ throw JsonError("Json: back() on a non-container value");
390
+ }
391
+
392
+ const Json& Json::back() const {
393
+ if (is_array()) { if (arr_->empty()) throw JsonError("Json: back() on empty array"); return arr_->back(); }
394
+ if (is_object()) { if (obj_->empty()) throw JsonError("Json: back() on empty object"); return (obj_->end() - 1)->second; }
395
+ throw JsonError("Json: back() on a non-container value");
396
+ }
397
+
398
+ void Json::push_back(const Json& value) {
399
+ if (is_null()) { type_ = Type::Array; arr_ = new Array(); }
400
+ if (!is_array()) throw JsonError("Json: push_back on a non-array value");
401
+ arr_->push_back(value);
402
+ }
403
+
404
+ void Json::push_back(Json&& value) {
405
+ if (is_null()) { type_ = Type::Array; arr_ = new Array(); }
406
+ if (!is_array()) throw JsonError("Json: push_back on a non-array value");
407
+ arr_->push_back(std::move(value));
408
+ }
409
+
410
+ void Json::clear() {
411
+ switch (type_) {
412
+ case Type::Array: arr_->clear(); break;
413
+ case Type::Object: obj_->clear(); break;
414
+ case Type::String: str_->clear(); break;
415
+ case Type::Integer: int_ = 0; break;
416
+ case Type::Unsigned: uint_ = 0; break;
417
+ case Type::Float: float_ = 0.0; break;
418
+ case Type::Boolean: bool_ = false; break;
419
+ default: break;
420
+ }
421
+ }
422
+
423
+ Json::Array& Json::as_array() {
424
+ if (!is_array()) throw JsonError("Json: value is not an array");
425
+ return *arr_;
426
+ }
427
+ const Json::Array& Json::as_array() const {
428
+ if (!is_array()) throw JsonError("Json: value is not an array");
429
+ return *arr_;
430
+ }
431
+ Json::Object& Json::as_object() {
432
+ if (!is_object()) throw JsonError("Json: value is not an object");
433
+ return *obj_;
434
+ }
435
+ const Json::Object& Json::as_object() const {
436
+ if (!is_object()) throw JsonError("Json: value is not an object");
437
+ return *obj_;
438
+ }
439
+
440
+ // ── Equality ────────────────────────────────────────────────────────────────
441
+
442
+ bool Json::operator==(const Json& o) const {
443
+ // Numbers compare by mathematical value across the three numeric kinds.
444
+ if (is_number() && o.is_number()) {
445
+ if (is_number_float() || o.is_number_float()) return as_double() == o.as_double();
446
+ if (type_ == Type::Unsigned && o.type_ == Type::Unsigned) return uint_ == o.uint_;
447
+ if (type_ == Type::Integer && o.type_ == Type::Integer) return int_ == o.int_;
448
+ // Mixed signed/unsigned: a negative signed value never equals an unsigned.
449
+ auto eq = [](int64_t s, uint64_t u) {
450
+ return s >= 0 && static_cast<uint64_t>(s) == u;
451
+ };
452
+ if (type_ == Type::Integer) return eq(int_, o.uint_);
453
+ return eq(o.int_, uint_);
454
+ }
455
+
456
+ if (type_ != o.type_) return false;
457
+ switch (type_) {
458
+ case Type::Null:
459
+ case Type::Discarded: return true;
460
+ case Type::Boolean: return bool_ == o.bool_;
461
+ case Type::String: return *str_ == *o.str_;
462
+ case Type::Array: return *arr_ == *o.arr_; // element-wise via Json::operator==
463
+ case Type::Object: return *obj_ == *o.obj_; // order-independent
464
+ default: return false;
465
+ }
466
+ }
467
+
468
+ // ── Serialisation ───────────────────────────────────────────────────────────
469
+
470
+ namespace {
471
+
472
+ void dump_string(std::string& out, const std::string& s) {
473
+ // Escapes are rare in real payloads (ids, ips, agent strings carry none), so
474
+ // we copy maximal runs of pass-through bytes in one append() and only break
475
+ // the run for a character that needs escaping.
476
+ out += '"';
477
+ const char* const begin = s.data();
478
+ const char* const end = begin + s.size();
479
+ const char* run = begin; // start of the current pass-through run
480
+ for (const char* p = begin; p != end; ++p) {
481
+ const char* esc = nullptr; // two-char escape for *p, if any
482
+ switch (*p) {
483
+ case '"': esc = "\\\""; break;
484
+ case '\\': esc = "\\\\"; break;
485
+ case '\b': esc = "\\b"; break;
486
+ case '\f': esc = "\\f"; break;
487
+ case '\n': esc = "\\n"; break;
488
+ case '\r': esc = "\\r"; break;
489
+ case '\t': esc = "\\t"; break;
490
+ default: break;
491
+ }
492
+ if (esc) {
493
+ out.append(run, static_cast<std::size_t>(p - run));
494
+ out += esc;
495
+ run = p + 1;
496
+ } else if (static_cast<unsigned char>(*p) < 0x20) {
497
+ // Other control characters use the \uXXXX form.
498
+ out.append(run, static_cast<std::size_t>(p - run));
499
+ char buf[7];
500
+ std::snprintf(buf, sizeof buf, "\\u%04x", static_cast<unsigned char>(*p));
501
+ out += buf;
502
+ run = p + 1;
503
+ }
504
+ // Printable ASCII and raw UTF-8 bytes stay in the run.
505
+ }
506
+ out.append(run, static_cast<std::size_t>(end - run));
507
+ out += '"';
508
+ }
509
+
510
+ // ── Fast integer formatting ──────────────────────────────────────────────────
511
+ //
512
+ // Integers are emitted two decimal digits at a time by indexing a table of all
513
+ // hundred digit pairs, which halves the number of (comparatively slow) integer
514
+ // divisions a digit-at-a-time loop performs. This is the well-worn approach used
515
+ // inside libstdc++, abseil and fmt; spelling it out keeps the fast path byte-for
516
+ // -byte identical on every toolchain instead of silently degrading to an
517
+ // allocating std::to_string wherever std::to_chars' integer overloads are
518
+ // missing.
519
+
520
+ // Every two-digit value "00", "01", … "99" laid out back to back, so the digits
521
+ // of `n` live at kDigitPairs[2*n] and kDigitPairs[2*n + 1].
522
+ constexpr char kDigitPairs[] =
523
+ "00010203040506070809"
524
+ "10111213141516171819"
525
+ "20212223242526272829"
526
+ "30313233343536373839"
527
+ "40414243444546474849"
528
+ "50515253545556575859"
529
+ "60616263646566676869"
530
+ "70717273747576777879"
531
+ "80818283848586878889"
532
+ "90919293949596979899";
533
+
534
+ // Write the decimal digits of an unsigned magnitude backward, finishing at
535
+ // `last`, and return a pointer to the most significant digit produced. The
536
+ // caller owns the buffer; 20 digits span the full 64-bit range.
537
+ inline char* write_decimal(char* last, uint64_t value) {
538
+ while (value >= 100) {
539
+ const unsigned pair = static_cast<unsigned>(value % 100) * 2;
540
+ value /= 100;
541
+ *--last = kDigitPairs[pair + 1];
542
+ *--last = kDigitPairs[pair];
543
+ }
544
+ if (value >= 10) {
545
+ const unsigned pair = static_cast<unsigned>(value) * 2;
546
+ *--last = kDigitPairs[pair + 1];
547
+ *--last = kDigitPairs[pair];
548
+ } else {
549
+ *--last = static_cast<char>('0' + value);
550
+ }
551
+ return last;
552
+ }
553
+
554
+ // Append the shortest decimal form of an integer with no heap allocation.
555
+ template <typename Int>
556
+ void dump_int(std::string& out, Int v) {
557
+ char buf[21]; // 20 digits + sign: the 64-bit worst case
558
+ char* const last = buf + sizeof buf;
559
+ char* first;
560
+ if constexpr (std::is_signed<Int>::value) {
561
+ uint64_t mag = static_cast<uint64_t>(v);
562
+ if (v < 0) {
563
+ mag = ~mag + 1; // two's-complement magnitude (safe at INT64_MIN)
564
+ first = write_decimal(last, mag);
565
+ *--first = '-';
566
+ } else {
567
+ first = write_decimal(last, mag);
568
+ }
569
+ } else {
570
+ first = write_decimal(last, static_cast<uint64_t>(v));
571
+ }
572
+ out.append(first, last);
573
+ }
574
+
575
+ void dump_double(std::string& out, double d) {
576
+ if (std::isnan(d) || std::isinf(d)) { out += "null"; return; } // JSON has no NaN/Inf
577
+
578
+ char buf[32];
579
+ char* last;
580
+ #if LIBRATS_JSON_CHARCONV
581
+ // to_chars (no precision) yields the shortest string that round-trips exactly.
582
+ last = std::to_chars(buf, buf + sizeof buf, d).ptr;
583
+ #else
584
+ // Fallback: grow precision until the value round-trips.
585
+ int n = 0;
586
+ for (int prec = 15; prec <= 17; ++prec) {
587
+ n = std::snprintf(buf, sizeof buf, "%.*g", prec, d);
588
+ if (std::strtod(buf, nullptr) == d) break;
589
+ }
590
+ last = buf + n;
591
+ #endif
592
+ out.append(buf, last);
593
+
594
+ // Keep it recognisable as a float so it parses back to a float, not an int:
595
+ // a token with no '.', 'e' or 'E' (e.g. "2") gets a trailing ".0".
596
+ for (const char* q = buf; q != last; ++q) {
597
+ if (*q == '.' || *q == 'e' || *q == 'E') return;
598
+ }
599
+ out += ".0";
600
+ }
601
+
602
+ void newline_indent(std::string& out, int indent, int depth) {
603
+ if (indent < 0) return;
604
+ out += '\n';
605
+ out.append(static_cast<std::size_t>(indent) * static_cast<std::size_t>(depth), ' ');
606
+ }
607
+
608
+ } // namespace
609
+
610
+ void Json::dump_to(std::string& out, int indent, int depth) const {
611
+ switch (type_) {
612
+ case Type::Null: out += "null"; break;
613
+ case Type::Discarded: out += "null"; break; // dumping a discarded value falls back to null
614
+ case Type::Boolean: out += bool_ ? "true" : "false"; break;
615
+ case Type::Integer: dump_int(out, int_); break;
616
+ case Type::Unsigned: dump_int(out, uint_); break;
617
+ case Type::Float: dump_double(out, float_); break;
618
+ case Type::String: dump_string(out, *str_); break;
619
+ case Type::Array: {
620
+ if (arr_->empty()) { out += "[]"; break; }
621
+ out += '[';
622
+ bool first = true;
623
+ for (const Json& el : *arr_) {
624
+ if (!first) out += ',';
625
+ first = false;
626
+ newline_indent(out, indent, depth + 1);
627
+ el.dump_to(out, indent, depth + 1);
628
+ }
629
+ newline_indent(out, indent, depth);
630
+ out += ']';
631
+ break;
632
+ }
633
+ case Type::Object: {
634
+ if (obj_->empty()) { out += "{}"; break; }
635
+ out += '{';
636
+ bool first = true;
637
+ for (const auto& kv : *obj_) {
638
+ if (!first) out += ',';
639
+ first = false;
640
+ newline_indent(out, indent, depth + 1);
641
+ dump_string(out, kv.first);
642
+ out += ':';
643
+ if (indent >= 0) out += ' ';
644
+ kv.second.dump_to(out, indent, depth + 1);
645
+ }
646
+ newline_indent(out, indent, depth);
647
+ out += '}';
648
+ break;
649
+ }
650
+ }
651
+ }
652
+
653
+ std::string Json::dump(int indent) const {
654
+ std::string out;
655
+ dump_to(out, indent, 0);
656
+ return out;
657
+ }
658
+
659
+ // ── Parsing ─────────────────────────────────────────────────────────────────
660
+
661
+ class JsonParser {
662
+ public:
663
+ JsonParser(const char* begin, const char* end) : p_(begin), end_(end) {}
664
+
665
+ Json parse() {
666
+ skip_ws();
667
+ if (p_ == end_) error("empty document");
668
+ Json v = parse_value(0);
669
+ skip_ws();
670
+ if (p_ != end_) error("unexpected trailing characters");
671
+ return v;
672
+ }
673
+
674
+ private:
675
+ const char* p_;
676
+ const char* end_;
677
+ static constexpr int kMaxDepth = 1000;
678
+
679
+ [[noreturn]] void error(const std::string& msg) const {
680
+ throw JsonError("JSON parse error: " + msg);
681
+ }
682
+
683
+ static bool is_digit(char c) { return c >= '0' && c <= '9'; }
684
+
685
+ void skip_ws() {
686
+ while (p_ != end_) {
687
+ char c = *p_;
688
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r') ++p_;
689
+ else break;
690
+ }
691
+ }
692
+
693
+ char peek() const { return p_ != end_ ? *p_ : '\0'; }
694
+
695
+ Json parse_value(int depth) {
696
+ if (depth > kMaxDepth) error("maximum nesting depth exceeded");
697
+ skip_ws();
698
+ if (p_ == end_) error("unexpected end of input");
699
+ switch (*p_) {
700
+ case '{': return parse_object(depth);
701
+ case '[': return parse_array(depth);
702
+ case '"': return Json(parse_string());
703
+ case 't': case 'f': return parse_bool();
704
+ case 'n': return parse_null();
705
+ case '-': return parse_number();
706
+ default:
707
+ if (is_digit(*p_)) return parse_number();
708
+ error(std::string("unexpected character '") + *p_ + "'");
709
+ }
710
+ }
711
+
712
+ Json parse_object(int depth) {
713
+ ++p_; // consume '{'
714
+ Json obj = Json::object();
715
+ Json::Object& o = obj.as_object();
716
+ skip_ws();
717
+ if (peek() == '}') { ++p_; return obj; }
718
+ while (true) {
719
+ skip_ws();
720
+ if (peek() != '"') error("expected string key in object");
721
+ std::string key = parse_string();
722
+ skip_ws();
723
+ if (peek() != ':') error("expected ':' after object key");
724
+ ++p_;
725
+ o[std::move(key)] = parse_value(depth + 1);
726
+ skip_ws();
727
+ char c = peek();
728
+ if (c == ',') { ++p_; continue; }
729
+ if (c == '}') { ++p_; break; }
730
+ error("expected ',' or '}' in object");
731
+ }
732
+ return obj;
733
+ }
734
+
735
+ Json parse_array(int depth) {
736
+ ++p_; // consume '['
737
+ Json arr = Json::array();
738
+ Json::Array& a = arr.as_array();
739
+ skip_ws();
740
+ if (peek() == ']') { ++p_; return arr; }
741
+ while (true) {
742
+ a.push_back(parse_value(depth + 1));
743
+ skip_ws();
744
+ char c = peek();
745
+ if (c == ',') { ++p_; continue; }
746
+ if (c == ']') { ++p_; break; }
747
+ error("expected ',' or ']' in array");
748
+ }
749
+ return arr;
750
+ }
751
+
752
+ Json parse_bool() {
753
+ if (end_ - p_ >= 4 && std::strncmp(p_, "true", 4) == 0) { p_ += 4; return Json(true); }
754
+ if (end_ - p_ >= 5 && std::strncmp(p_, "false", 5) == 0) { p_ += 5; return Json(false); }
755
+ error("invalid literal");
756
+ }
757
+
758
+ Json parse_null() {
759
+ if (end_ - p_ >= 4 && std::strncmp(p_, "null", 4) == 0) { p_ += 4; return Json(nullptr); }
760
+ error("invalid literal");
761
+ }
762
+
763
+ unsigned parse_hex4() {
764
+ if (end_ - p_ < 4) error("invalid \\u escape");
765
+ unsigned v = 0;
766
+ for (int i = 0; i < 4; ++i) {
767
+ char h = *p_++;
768
+ v <<= 4;
769
+ if (h >= '0' && h <= '9') v |= static_cast<unsigned>(h - '0');
770
+ else if (h >= 'a' && h <= 'f') v |= static_cast<unsigned>(h - 'a' + 10);
771
+ else if (h >= 'A' && h <= 'F') v |= static_cast<unsigned>(h - 'A' + 10);
772
+ else error("invalid hex digit in \\u escape");
773
+ }
774
+ return v;
775
+ }
776
+
777
+ static void append_utf8(std::string& out, unsigned cp) {
778
+ if (cp <= 0x7F) {
779
+ out += static_cast<char>(cp);
780
+ } else if (cp <= 0x7FF) {
781
+ out += static_cast<char>(0xC0 | (cp >> 6));
782
+ out += static_cast<char>(0x80 | (cp & 0x3F));
783
+ } else if (cp <= 0xFFFF) {
784
+ out += static_cast<char>(0xE0 | (cp >> 12));
785
+ out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
786
+ out += static_cast<char>(0x80 | (cp & 0x3F));
787
+ } else {
788
+ out += static_cast<char>(0xF0 | (cp >> 18));
789
+ out += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
790
+ out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
791
+ out += static_cast<char>(0x80 | (cp & 0x3F));
792
+ }
793
+ }
794
+
795
+ // Scan from the current position to the closing quote, counting '\X' as two
796
+ // bytes so an escaped quote isn't mistaken for the end. Returns a pointer to
797
+ // the closing '"' (or end_ if unterminated — the caller's decode loop then
798
+ // reports it). Used only to size the decode buffer; all escape validation
799
+ // still happens in parse_string, so this stays deliberately permissive.
800
+ const char* scan_to_close() const {
801
+ const char* s = p_;
802
+ while (s != end_) {
803
+ if (*s == '"') return s;
804
+ if (*s == '\\' && ++s == end_) break;
805
+ ++s;
806
+ }
807
+ return end_;
808
+ }
809
+
810
+ std::string parse_string() {
811
+ ++p_; // consume opening '"'
812
+ std::string out;
813
+ // Most string bytes need no translation, so copy maximal escape-free
814
+ // runs in a single append() and only stop for '"', '\\' or a control
815
+ // character. `run` marks the start of the run still to be flushed.
816
+ // `str_begin` anchors the whole content for one-shot buffer sizing.
817
+ const char* const str_begin = p_;
818
+ const char* run = p_;
819
+ bool sized = false;
820
+ while (true) {
821
+ if (p_ == end_) error("unterminated string");
822
+ unsigned char c = static_cast<unsigned char>(*p_);
823
+ if (c == '"') { out.append(run, static_cast<std::size_t>(p_ - run)); ++p_; break; }
824
+ if (c == '\\') {
825
+ if (!sized) {
826
+ // First escape: an escape-free string never reaches here and
827
+ // stays on the single-append fast path. Once we must decode,
828
+ // the result can be no longer than the raw bytes up to the
829
+ // closing quote, so reserve that exactly once — every later
830
+ // append() then stays in place instead of reallocating.
831
+ out.reserve(static_cast<std::size_t>(scan_to_close() - str_begin));
832
+ sized = true;
833
+ }
834
+ out.append(run, static_cast<std::size_t>(p_ - run));
835
+ ++p_; // consume backslash
836
+ if (p_ == end_) error("unterminated escape sequence");
837
+ char e = *p_++;
838
+ switch (e) {
839
+ case '"': out += '"'; break;
840
+ case '\\': out += '\\'; break;
841
+ case '/': out += '/'; break;
842
+ case 'b': out += '\b'; break;
843
+ case 'f': out += '\f'; break;
844
+ case 'n': out += '\n'; break;
845
+ case 'r': out += '\r'; break;
846
+ case 't': out += '\t'; break;
847
+ case 'u': {
848
+ unsigned cp = parse_hex4();
849
+ if (cp >= 0xD800 && cp <= 0xDBFF) {
850
+ // High surrogate must be followed by a low surrogate.
851
+ if (end_ - p_ < 2 || p_[0] != '\\' || p_[1] != 'u')
852
+ error("invalid surrogate pair");
853
+ p_ += 2;
854
+ unsigned lo = parse_hex4();
855
+ if (lo < 0xDC00 || lo > 0xDFFF) error("invalid low surrogate");
856
+ cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
857
+ } else if (cp >= 0xDC00 && cp <= 0xDFFF) {
858
+ error("unexpected low surrogate");
859
+ }
860
+ append_utf8(out, cp);
861
+ break;
862
+ }
863
+ default: error("invalid escape sequence");
864
+ }
865
+ run = p_; // next run resumes after the escape
866
+ } else if (c < 0x20) {
867
+ error("unescaped control character in string");
868
+ } else {
869
+ ++p_; // ordinary byte: defer the copy until the run is flushed
870
+ }
871
+ }
872
+ return out;
873
+ }
874
+
875
+ Json parse_number() {
876
+ const char* start = p_;
877
+ bool is_float = false;
878
+
879
+ if (peek() == '-') ++p_;
880
+ if (p_ == end_) error("invalid number");
881
+
882
+ if (peek() == '0') {
883
+ ++p_;
884
+ } else if (is_digit(peek())) {
885
+ while (p_ != end_ && is_digit(*p_)) ++p_;
886
+ } else {
887
+ error("invalid number");
888
+ }
889
+
890
+ if (p_ != end_ && *p_ == '.') {
891
+ is_float = true;
892
+ ++p_;
893
+ if (p_ == end_ || !is_digit(*p_)) error("invalid number: expected digits after '.'");
894
+ while (p_ != end_ && is_digit(*p_)) ++p_;
895
+ }
896
+
897
+ if (p_ != end_ && (*p_ == 'e' || *p_ == 'E')) {
898
+ is_float = true;
899
+ ++p_;
900
+ if (p_ != end_ && (*p_ == '+' || *p_ == '-')) ++p_;
901
+ if (p_ == end_ || !is_digit(*p_)) error("invalid number: expected digits in exponent");
902
+ while (p_ != end_ && is_digit(*p_)) ++p_;
903
+ }
904
+
905
+ // The grammar above is already validated, so the only conversion
906
+ // outcome we still handle is integer overflow → fall back to double.
907
+ return finish_number(start, is_float);
908
+ }
909
+
910
+ // Parse [start, end) as a double. On magnitude overflow std::from_chars
911
+ // reports result_out_of_range and — on libstdc++ — leaves the out-param
912
+ // untouched, which would silently yield 0.0; map that to ±infinity instead,
913
+ // matching the strtod fallback path and nlohmann (Json then dumps it as null).
914
+ static double parse_double(const char* start, const char* end) {
915
+ double d = 0.0;
916
+ #if LIBRATS_JSON_FROM_CHARS_FLOAT
917
+ auto res = std::from_chars(start, end, d);
918
+ if (res.ec == std::errc::result_out_of_range)
919
+ d = (*start == '-') ? -HUGE_VAL : HUGE_VAL;
920
+ #else
921
+ // No floating-point from_chars (e.g. libc++): strtod over a NUL-terminated
922
+ // copy. strtod already returns ±HUGE_VAL on overflow, so no fixup needed.
923
+ std::string num(start, end);
924
+ d = std::strtod(num.c_str(), nullptr);
925
+ #endif
926
+ return d;
927
+ }
928
+
929
+ // Convert the already-scanned token [start, p_) into a Json number.
930
+ Json finish_number(const char* start, bool is_float) {
931
+ #if LIBRATS_JSON_CHARCONV
932
+ if (is_float) return Json(parse_double(start, p_));
933
+ if (*start == '-') {
934
+ int64_t v = 0;
935
+ if (std::from_chars(start, p_, v).ec == std::errc{}) return Json(v);
936
+ } else {
937
+ uint64_t v = 0;
938
+ if (std::from_chars(start, p_, v).ec == std::errc{}) {
939
+ // Prefer signed storage when it fits, so small positive ints
940
+ // compare equal to literal-constructed ones however they were built.
941
+ if (v <= static_cast<uint64_t>(INT64_MAX))
942
+ return Json(static_cast<int64_t>(v));
943
+ return Json(v);
944
+ }
945
+ }
946
+ // Integer overflowed 64 bits: represent it (approximately) as a double.
947
+ return Json(parse_double(start, p_));
948
+ #else
949
+ std::string num(start, p_);
950
+ if (is_float) return Json(std::strtod(num.c_str(), nullptr));
951
+
952
+ errno = 0;
953
+ if (num[0] == '-') {
954
+ long long v = std::strtoll(num.c_str(), nullptr, 10);
955
+ if (errno == ERANGE) return Json(std::strtod(num.c_str(), nullptr));
956
+ return Json(static_cast<int64_t>(v));
957
+ }
958
+ unsigned long long v = std::strtoull(num.c_str(), nullptr, 10);
959
+ if (errno == ERANGE) return Json(std::strtod(num.c_str(), nullptr));
960
+ if (v <= static_cast<unsigned long long>(INT64_MAX))
961
+ return Json(static_cast<int64_t>(v));
962
+ return Json(static_cast<uint64_t>(v));
963
+ #endif
964
+ }
965
+ };
966
+
967
+ Json Json::parse(const std::string& text, std::nullptr_t, bool allow_exceptions) {
968
+ const char* begin = text.data();
969
+ const char* end = begin + text.size();
970
+ // Skip a leading UTF-8 byte-order mark if present.
971
+ if (text.size() >= 3 && static_cast<unsigned char>(begin[0]) == 0xEF &&
972
+ static_cast<unsigned char>(begin[1]) == 0xBB &&
973
+ static_cast<unsigned char>(begin[2]) == 0xBF) {
974
+ begin += 3;
975
+ }
976
+ try {
977
+ JsonParser parser(begin, end);
978
+ return parser.parse();
979
+ } catch (const std::exception&) {
980
+ if (allow_exceptions) throw;
981
+ return make_discarded();
982
+ }
983
+ }
984
+
985
+ Json Json::parse(const char* text, std::nullptr_t, bool allow_exceptions) {
986
+ return parse(std::string(text ? text : ""), nullptr, allow_exceptions);
987
+ }
988
+
989
+ // ── Stream operators ────────────────────────────────────────────────────────
990
+
991
+ std::istream& operator>>(std::istream& is, Json& j) {
992
+ std::string content((std::istreambuf_iterator<char>(is)),
993
+ std::istreambuf_iterator<char>());
994
+ j = Json::parse(content); // throws JsonError on malformed input
995
+ return is;
996
+ }
997
+
998
+ std::ostream& operator<<(std::ostream& os, const Json& j) {
999
+ return os << j.dump();
1000
+ }
1001
+
1002
+ } // namespace librats