From 068ef188e751c23fc190b9a4cb21e485e74704b5 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Tue, 16 Jun 2026 11:18:22 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + CMakeLists.txt | 8 + config/1221/config.json | 157 + config/S79hostapd | 25 + config/S90E-CAP | 26 + config/config.json | 302 + config/config.json.back | 302 + config/config2.json | 251 + config/data_progress_config.json | 9 + config/deploy.sh | 205 + config/interface | 36 + config/polkit 规则 | 20 + config/pt | 226 + config/restart_net_service.sh | 12 + config/server_config_win.json | 25 + config/update.sh | 55 + config/upgrade.bash | 113 + config/x86_linux/config.json | 174 + config/x86_linux/data_progress_config.json | 9 + config/前端任务 | 7 + doc/10003 | 74 + doc/10003_zh | 39 + doc/11.txt | 78 + doc/HULC状态消息.txt | 36 + doc/icao解码.txt | 50 + doc/radarcape_状态消息.txt | 65 + doc/模块数据格式分析 | 53 + doc/泰山派开发记录.md | 418 + doc/网页文档.md | 36 + logrotate | 3 + main.cmake | 153 + module/Local_Server.rar | Bin 0 -> 96523 bytes module/Local_Server/Aircraft/Aircraft.cpp | 1 + module/Local_Server/Aircraft/Aircraft.h | 79 + module/Local_Server/Aircraft/Flight_VTO.cpp | 1 + module/Local_Server/Aircraft/Flight_VTO.h | 67 + .../Local_Server/Aircraft/aircraftlist_json.h | 260 + module/Local_Server/Aircraft/global.h | 38 + module/Local_Server/Crawler/Crawler.cpp | 96 + module/Local_Server/Crawler/Crawler.h | 96 + module/Local_Server/DSP/DSP_Config.cpp | 382 + module/Local_Server/DSP/DSP_Config.h | 132 + module/Local_Server/DSP/export.h | 3 + module/Local_Server/DSP/global.h | 34 + module/Local_Server/Data_Feed/Data_Feed.cpp | 177 + module/Local_Server/Data_Feed/Data_Feed.h | 356 + .../Data_Feed/data_feed_thread.cpp | 230 + module/Local_Server/Data_Feed/export.h | 2 + module/Local_Server/Data_Feed/global.h | 6 + .../Local_Server/Data_Source/BaseStation.cpp | 43 + module/Local_Server/Data_Source/BaseStation.h | 20 + .../Local_Server/Data_Source/Data_Source.cpp | 1078 ++ module/Local_Server/Data_Source/Data_Source.h | 527 + module/Local_Server/Data_Source/Database.cpp | 339 + module/Local_Server/Data_Source/Database.h | 228 + module/Local_Server/Data_Source/PlayBack.cpp | 1 + module/Local_Server/Data_Source/PlayBack.h | 33 + module/Local_Server/Data_Source/export.h | 5 + module/Local_Server/Data_Source/global.h | 38 + .../Adsblol_Vrs_Standing_Data.cpp | 18 + .../External_Database/External_Database.cpp | 581 + .../Faa_Aircraft_Registry.cpp | 115 + .../External_Database/Icao_Doc_8643.cpp | 18 + .../Mictronics_Aircraft_Database.cpp | 13 + .../OpenSky_Aircraft_Database.cpp | 24 + .../OpenSky_FlightData_Api.cpp | 30 + .../External_Database/Resource_Utils.cpp | 458 + .../External_Database/Resource_Utils.h | 67 + .../External_Database/Resources.h | 17 + .../Tar1090_Db_Aircraft_Csv_Gz.cpp | 12 + .../Vradarserver_Standing_Data.cpp | 19 + .../External_Database/Vrs_Airports.cpp | 18 + .../External_Database/Vrs_Routes.cpp | 17 + .../Wiedehopf_Tar1090_Db.cpp | 12 + .../Local_Server/External_Database/export.h | 2 + .../Local_Server/External_Database/global.h | 159 + .../State_Report/State_Report.cpp | 13 + .../Local_Server/State_Report/State_Report.h | 22 + module/Local_Server/global_include.h | 419 + module/Local_Server/lib9002/DDCSampTable.h | 28 + module/Local_Server/server/Config.cpp | 192 + module/Local_Server/server/Config.h | 357 + module/Local_Server/server/Global.cpp | 385 + module/Local_Server/server/Global.h | 239 + module/Local_Server/server/MLAT.cpp | 194 + module/Local_Server/server/MLAT.h | 80 + module/Local_Server/server/Mode_Msg_Buffer.h | 58 + .../Local_Server/server/Performance_Monitor.h | 85 + .../server/Source_Feed_Relation.cpp | 80 + .../server/Source_Feed_Relation.h | 95 + .../Local_Server/server/WebSocket_Manager.cpp | 115 + .../Local_Server/server/WebSocket_Manager.h | 36 + module/Local_Server/server/libarchive.cpp | 678 + module/Local_Server/server/libarchive.h | 60 + module/Local_Server/server/server.cpp | 679 + module/Local_Server_main.cpp | 229 + module/Mock/main.cpp | 56 + module/Mock/mock_core.cpp | 1 + module/Mock/mock_core.h | 84 + module/Radarcape_Core/cpp-httplib/doc.txt | 1 + module/Radarcape_Core/cpp-httplib/httplib.h | 11698 ++++++++++++++++ module/Radarcape_Core/main.cpp | 9 + module/Radarcape_Core/todo | 9 + module/crc_check.cpp | 240 + module/dll_source/Dll_Global.cpp | 266 + module/dll_source/Dll_Global.h | 94 + module/dll_source/device_uhd_c.h | 98 + module/dll_source/device_uhd_struct.h | 259 + module/dll_source/error_callback.h | 19 + module/dll_source/main.cpp | 207 + module/dll_source/main.h | 50 + module/dll_source_lzy/114/out.tar | Bin 0 -> 1034240 bytes .../114/out/lib/.device_uhd_c.cpp.swp | Bin 0 -> 20480 bytes .../114/out/lib/.idea/.gitignore | 8 + .../114/out/lib/.idea/deployment.xml | 14 + .../114/out/lib/.idea/editor.xml | 335 + .../dll_source_lzy/114/out/lib/.idea/lib.iml | 8 + .../dll_source_lzy/114/out/lib/.idea/misc.xml | 6 + .../114/out/lib/.idea/modules.xml | 8 + .../dll_source_lzy/114/out/lib/DDCSampTable.h | 28 + .../114/out/lib/FIRGenLowpass_no_ipp.h | 105 + module/dll_source_lzy/114/out/lib/a.dat | 0 module/dll_source_lzy/114/out/lib/cJSON.c | 3164 +++++ module/dll_source_lzy/114/out/lib/cJSON.h | 306 + .../114/out/lib/device_uhd_c.cpp | 1804 +++ .../dll_source_lzy/114/out/lib/device_uhd_c.h | 108 + .../114/out/lib/device_uhd_cpp.cpp | 2615 ++++ .../114/out/lib/device_uhd_cpp.h | 144 + .../114/out/lib/device_uhd_struct.h | 319 + .../114/out/lib/error_callback.cpp | 43 + .../114/out/lib/error_callback.h | 17 + .../dll_source_lzy/114/out/lib/hulc/Bit.cpp | 761 + module/dll_source_lzy/114/out/lib/hulc/Bit.h | 309 + .../114/out/lib/hulc/DataOutputFormats.cpp | 106 + .../114/out/lib/hulc/DataOutputFormats.h | 531 + .../dll_source_lzy/114/out/lib/hulc/Json.cpp | 420 + module/dll_source_lzy/114/out/lib/hulc/Json.h | 142 + .../114/out/lib/hulc/base_format.h | 74 + .../114/out/lib/hulc/magic_enum/export.h | 114 + .../lib/hulc/magic_enum/magic_enum/LICENSE | 21 + .../hulc/magic_enum/magic_enum/magic_enum.hpp | 1508 ++ .../magic_enum/magic_enum/magic_enum_all.hpp | 44 + .../magic_enum/magic_enum_containers.hpp | 1174 ++ .../magic_enum/magic_enum_flags.hpp | 222 + .../magic_enum/magic_enum_format.hpp | 114 + .../magic_enum/magic_enum/magic_enum_fuse.hpp | 89 + .../magic_enum/magic_enum_iostream.hpp | 117 + .../magic_enum/magic_enum_switch.hpp | 195 + .../magic_enum/magic_enum_utility.hpp | 138 + module/dll_source_lzy/114/out/lib/log.txt | 1443 ++ .../114/out/lib/makelib_common.h | 14 + module/dll_source_lzy/114/out/lib9002.so | Bin 0 -> 417448 bytes module/dll_source_lzy/114/丢包测试记录.txt | 70 + .../ecap/lib/.device_uhd_c.cpp.swp | Bin 0 -> 20480 bytes module/dll_source_lzy/ecap/lib/DDCSampTable.h | 28 + .../ecap/lib/FIRGenLowpass_no_ipp.h | 105 + module/dll_source_lzy/ecap/lib/cJSON.c | 3164 +++++ module/dll_source_lzy/ecap/lib/cJSON.h | 306 + .../dll_source_lzy/ecap/lib/device_uhd_c.cpp | 1531 ++ module/dll_source_lzy/ecap/lib/device_uhd_c.h | 97 + .../ecap/lib/device_uhd_cpp.cpp | 2152 +++ .../dll_source_lzy/ecap/lib/device_uhd_cpp.h | 113 + .../ecap/lib/device_uhd_struct.h | 322 + module/dll_source_lzy/ecap/lib/hulc/Bit.cpp | 761 + module/dll_source_lzy/ecap/lib/hulc/Bit.h | 309 + .../ecap/lib/hulc/DataOutputFormats.cpp | 106 + .../ecap/lib/hulc/DataOutputFormats.h | 531 + .../ecap/lib/hulc/base_format.h | 74 + .../ecap/lib/hulc/magic_enum/export.h | 114 + .../lib/hulc/magic_enum/magic_enum/LICENSE | 21 + .../hulc/magic_enum/magic_enum/magic_enum.hpp | 1508 ++ .../magic_enum/magic_enum/magic_enum_all.hpp | 44 + .../magic_enum/magic_enum_containers.hpp | 1174 ++ .../magic_enum/magic_enum_flags.hpp | 222 + .../magic_enum/magic_enum_format.hpp | 114 + .../magic_enum/magic_enum/magic_enum_fuse.hpp | 89 + .../magic_enum/magic_enum_iostream.hpp | 117 + .../magic_enum/magic_enum_switch.hpp | 195 + .../magic_enum/magic_enum_utility.hpp | 138 + .../dll_source_lzy/ecap/lib/makelib_common.h | 14 + module/dll_source_lzy/ecap/lib9002.so | Bin 0 -> 361136 bytes module/get_track_tool_main_backup.cpp | 277 + module/mlat_source/main.h | 27 + module/mlat_source/mlat_dll.cpp | 158 + module/test_drogon/main.cpp | 32 + module/test_memory_lose.cpp | 194 + script/api.http | 476 + script/old/ecap.service | 48 + script/old/heco22.service | 47 + script/rc.local | 5 + script/scp.bash | 53 + script/scp.ps1 | 31 + script/scp_wwwroot.bash | 33 + script/start_22.sh | 70 + script/upgrade.ps1 | 15 + script/重启fpga | 14 + ssh | 6 + todolist.txt | 10 + 初版升级步骤.txt | 21 + 多点定位数据模拟以及算法规范.txt | 28 + 多进程隔离.txt | 15 + 201 files changed, 57503 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 config/1221/config.json create mode 100644 config/S79hostapd create mode 100644 config/S90E-CAP create mode 100644 config/config.json create mode 100644 config/config.json.back create mode 100644 config/config2.json create mode 100644 config/data_progress_config.json create mode 100644 config/deploy.sh create mode 100644 config/interface create mode 100644 config/polkit 规则 create mode 100644 config/pt create mode 100644 config/restart_net_service.sh create mode 100644 config/server_config_win.json create mode 100644 config/update.sh create mode 100644 config/upgrade.bash create mode 100644 config/x86_linux/config.json create mode 100644 config/x86_linux/data_progress_config.json create mode 100644 config/前端任务 create mode 100644 doc/10003 create mode 100644 doc/10003_zh create mode 100644 doc/11.txt create mode 100644 doc/HULC状态消息.txt create mode 100644 doc/icao解码.txt create mode 100644 doc/radarcape_状态消息.txt create mode 100644 doc/模块数据格式分析 create mode 100644 doc/泰山派开发记录.md create mode 100644 doc/网页文档.md create mode 100644 logrotate create mode 100644 main.cmake create mode 100644 module/Local_Server.rar create mode 100644 module/Local_Server/Aircraft/Aircraft.cpp create mode 100644 module/Local_Server/Aircraft/Aircraft.h create mode 100644 module/Local_Server/Aircraft/Flight_VTO.cpp create mode 100644 module/Local_Server/Aircraft/Flight_VTO.h create mode 100644 module/Local_Server/Aircraft/aircraftlist_json.h create mode 100644 module/Local_Server/Aircraft/global.h create mode 100644 module/Local_Server/Crawler/Crawler.cpp create mode 100644 module/Local_Server/Crawler/Crawler.h create mode 100644 module/Local_Server/DSP/DSP_Config.cpp create mode 100644 module/Local_Server/DSP/DSP_Config.h create mode 100644 module/Local_Server/DSP/export.h create mode 100644 module/Local_Server/DSP/global.h create mode 100644 module/Local_Server/Data_Feed/Data_Feed.cpp create mode 100644 module/Local_Server/Data_Feed/Data_Feed.h create mode 100644 module/Local_Server/Data_Feed/data_feed_thread.cpp create mode 100644 module/Local_Server/Data_Feed/export.h create mode 100644 module/Local_Server/Data_Feed/global.h create mode 100644 module/Local_Server/Data_Source/BaseStation.cpp create mode 100644 module/Local_Server/Data_Source/BaseStation.h create mode 100644 module/Local_Server/Data_Source/Data_Source.cpp create mode 100644 module/Local_Server/Data_Source/Data_Source.h create mode 100644 module/Local_Server/Data_Source/Database.cpp create mode 100644 module/Local_Server/Data_Source/Database.h create mode 100644 module/Local_Server/Data_Source/PlayBack.cpp create mode 100644 module/Local_Server/Data_Source/PlayBack.h create mode 100644 module/Local_Server/Data_Source/export.h create mode 100644 module/Local_Server/Data_Source/global.h create mode 100644 module/Local_Server/External_Database/Adsblol_Vrs_Standing_Data.cpp create mode 100644 module/Local_Server/External_Database/External_Database.cpp create mode 100644 module/Local_Server/External_Database/Faa_Aircraft_Registry.cpp create mode 100644 module/Local_Server/External_Database/Icao_Doc_8643.cpp create mode 100644 module/Local_Server/External_Database/Mictronics_Aircraft_Database.cpp create mode 100644 module/Local_Server/External_Database/OpenSky_Aircraft_Database.cpp create mode 100644 module/Local_Server/External_Database/OpenSky_FlightData_Api.cpp create mode 100644 module/Local_Server/External_Database/Resource_Utils.cpp create mode 100644 module/Local_Server/External_Database/Resource_Utils.h create mode 100644 module/Local_Server/External_Database/Resources.h create mode 100644 module/Local_Server/External_Database/Tar1090_Db_Aircraft_Csv_Gz.cpp create mode 100644 module/Local_Server/External_Database/Vradarserver_Standing_Data.cpp create mode 100644 module/Local_Server/External_Database/Vrs_Airports.cpp create mode 100644 module/Local_Server/External_Database/Vrs_Routes.cpp create mode 100644 module/Local_Server/External_Database/Wiedehopf_Tar1090_Db.cpp create mode 100644 module/Local_Server/External_Database/export.h create mode 100644 module/Local_Server/External_Database/global.h create mode 100644 module/Local_Server/State_Report/State_Report.cpp create mode 100644 module/Local_Server/State_Report/State_Report.h create mode 100644 module/Local_Server/global_include.h create mode 100644 module/Local_Server/lib9002/DDCSampTable.h create mode 100644 module/Local_Server/server/Config.cpp create mode 100644 module/Local_Server/server/Config.h create mode 100644 module/Local_Server/server/Global.cpp create mode 100644 module/Local_Server/server/Global.h create mode 100644 module/Local_Server/server/MLAT.cpp create mode 100644 module/Local_Server/server/MLAT.h create mode 100644 module/Local_Server/server/Mode_Msg_Buffer.h create mode 100644 module/Local_Server/server/Performance_Monitor.h create mode 100644 module/Local_Server/server/Source_Feed_Relation.cpp create mode 100644 module/Local_Server/server/Source_Feed_Relation.h create mode 100644 module/Local_Server/server/WebSocket_Manager.cpp create mode 100644 module/Local_Server/server/WebSocket_Manager.h create mode 100644 module/Local_Server/server/libarchive.cpp create mode 100644 module/Local_Server/server/libarchive.h create mode 100644 module/Local_Server/server/server.cpp create mode 100644 module/Local_Server_main.cpp create mode 100644 module/Mock/main.cpp create mode 100644 module/Mock/mock_core.cpp create mode 100644 module/Mock/mock_core.h create mode 100644 module/Radarcape_Core/cpp-httplib/doc.txt create mode 100644 module/Radarcape_Core/cpp-httplib/httplib.h create mode 100644 module/Radarcape_Core/main.cpp create mode 100644 module/Radarcape_Core/todo create mode 100644 module/crc_check.cpp create mode 100644 module/dll_source/Dll_Global.cpp create mode 100644 module/dll_source/Dll_Global.h create mode 100644 module/dll_source/device_uhd_c.h create mode 100644 module/dll_source/device_uhd_struct.h create mode 100644 module/dll_source/error_callback.h create mode 100644 module/dll_source/main.cpp create mode 100644 module/dll_source/main.h create mode 100644 module/dll_source_lzy/114/out.tar create mode 100644 module/dll_source_lzy/114/out/lib/.device_uhd_c.cpp.swp create mode 100644 module/dll_source_lzy/114/out/lib/.idea/.gitignore create mode 100644 module/dll_source_lzy/114/out/lib/.idea/deployment.xml create mode 100644 module/dll_source_lzy/114/out/lib/.idea/editor.xml create mode 100644 module/dll_source_lzy/114/out/lib/.idea/lib.iml create mode 100644 module/dll_source_lzy/114/out/lib/.idea/misc.xml create mode 100644 module/dll_source_lzy/114/out/lib/.idea/modules.xml create mode 100644 module/dll_source_lzy/114/out/lib/DDCSampTable.h create mode 100644 module/dll_source_lzy/114/out/lib/FIRGenLowpass_no_ipp.h create mode 100644 module/dll_source_lzy/114/out/lib/a.dat create mode 100644 module/dll_source_lzy/114/out/lib/cJSON.c create mode 100644 module/dll_source_lzy/114/out/lib/cJSON.h create mode 100644 module/dll_source_lzy/114/out/lib/device_uhd_c.cpp create mode 100644 module/dll_source_lzy/114/out/lib/device_uhd_c.h create mode 100644 module/dll_source_lzy/114/out/lib/device_uhd_cpp.cpp create mode 100644 module/dll_source_lzy/114/out/lib/device_uhd_cpp.h create mode 100644 module/dll_source_lzy/114/out/lib/device_uhd_struct.h create mode 100644 module/dll_source_lzy/114/out/lib/error_callback.cpp create mode 100644 module/dll_source_lzy/114/out/lib/error_callback.h create mode 100644 module/dll_source_lzy/114/out/lib/hulc/Bit.cpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/Bit.h create mode 100644 module/dll_source_lzy/114/out/lib/hulc/DataOutputFormats.cpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/DataOutputFormats.h create mode 100644 module/dll_source_lzy/114/out/lib/hulc/Json.cpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/Json.h create mode 100644 module/dll_source_lzy/114/out/lib/hulc/base_format.h create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/export.h create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/LICENSE create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum.hpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_all.hpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_containers.hpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_flags.hpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_format.hpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_fuse.hpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_iostream.hpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_switch.hpp create mode 100644 module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_utility.hpp create mode 100644 module/dll_source_lzy/114/out/lib/log.txt create mode 100644 module/dll_source_lzy/114/out/lib/makelib_common.h create mode 100644 module/dll_source_lzy/114/out/lib9002.so create mode 100644 module/dll_source_lzy/114/丢包测试记录.txt create mode 100644 module/dll_source_lzy/ecap/lib/.device_uhd_c.cpp.swp create mode 100644 module/dll_source_lzy/ecap/lib/DDCSampTable.h create mode 100644 module/dll_source_lzy/ecap/lib/FIRGenLowpass_no_ipp.h create mode 100644 module/dll_source_lzy/ecap/lib/cJSON.c create mode 100644 module/dll_source_lzy/ecap/lib/cJSON.h create mode 100644 module/dll_source_lzy/ecap/lib/device_uhd_c.cpp create mode 100644 module/dll_source_lzy/ecap/lib/device_uhd_c.h create mode 100644 module/dll_source_lzy/ecap/lib/device_uhd_cpp.cpp create mode 100644 module/dll_source_lzy/ecap/lib/device_uhd_cpp.h create mode 100644 module/dll_source_lzy/ecap/lib/device_uhd_struct.h create mode 100644 module/dll_source_lzy/ecap/lib/hulc/Bit.cpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/Bit.h create mode 100644 module/dll_source_lzy/ecap/lib/hulc/DataOutputFormats.cpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/DataOutputFormats.h create mode 100644 module/dll_source_lzy/ecap/lib/hulc/base_format.h create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/export.h create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/LICENSE create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum.hpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_all.hpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_containers.hpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_flags.hpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_format.hpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_fuse.hpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_iostream.hpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_switch.hpp create mode 100644 module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_utility.hpp create mode 100644 module/dll_source_lzy/ecap/lib/makelib_common.h create mode 100644 module/dll_source_lzy/ecap/lib9002.so create mode 100644 module/get_track_tool_main_backup.cpp create mode 100644 module/mlat_source/main.h create mode 100644 module/mlat_source/mlat_dll.cpp create mode 100644 module/test_drogon/main.cpp create mode 100644 module/test_memory_lose.cpp create mode 100644 script/api.http create mode 100644 script/old/ecap.service create mode 100644 script/old/heco22.service create mode 100644 script/rc.local create mode 100644 script/scp.bash create mode 100644 script/scp.ps1 create mode 100644 script/scp_wwwroot.bash create mode 100644 script/start_22.sh create mode 100644 script/upgrade.ps1 create mode 100644 script/重启fpga create mode 100644 ssh create mode 100644 todolist.txt create mode 100644 初版升级步骤.txt create mode 100644 多点定位数据模拟以及算法规范.txt create mode 100644 多进程隔离.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..153c761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/sqlite/ +/data/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ebe69f0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_LIST_DIR}) + include(${CMAKE_CURRENT_LIST_DIR}/main.cmake) +else() + +endif() + + + diff --git a/config/1221/config.json b/config/1221/config.json new file mode 100644 index 0000000..3678d35 --- /dev/null +++ b/config/1221/config.json @@ -0,0 +1,157 @@ +{ + "version": "v1.0.0", + "dsp": { + "library_path": "@/libdll_source.so", + "center_freq": 15000000, + "band_width": 10000, + "sample_rate": 60000, + "fft_point_number": 32768, + "fft_period_ms": 10, + "fft_win_type": "blkmc", + "url": "0.0.0.0", + "port": 6000, + "power_min": -200, + "power_max": 200, + "power_offset": -166, + "filename_prefix": "ECAP_", + "filename_suffix": ".dat", + "frame_rate": 30, + "debug": false + }, + "drogon": { + "app": { + "threads_num": 0 + } + }, + "mlat": { + "enable": false, + "merge": false, + "library_path": "@/mlat_source.dll", + "function_name": "read" + }, + "web_server": { + "webapp": "@/wwwroot", + "tiles": "@/../tiles" + }, + "device": { + "net": [ + { + "key": "device", + "ip": "192.168.1.75", + "netmask": "255.255.255.0", + "gateway": "192.168.1.1", + "port": 9081 + }, + { + "key": "wifi", + "ip": "192.168.10.193", + "netmask": "255.255.255.0", + "gateway": "192.168.10.1", + "port": 80 + } + ] + }, + "mode_acs": { + "timeout_seconds": 60, + "read_milliseconds": 100, + "monitor_msg_live": false, + "mode_s_max_num": 700000, + "mode_other_max_num": 10000, + "report_data_feed_msg_mum": 10000, + "data_feed": { + "empty_wait_milliseconds": 10, + "packet_byte_size": 1, + "list": [ + { + "key": "Port_10003", + "enable": true, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true + }, + "port": 10003 + }, + { + "key": "Port_10004", + "enable": false, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "BIN", + "use_status": false, + "mode_s_output_type": "DF_11_17_18", + "use_mode_ac": false + }, + "port": 10004 + }, + { + "key": "Port_10005", + "enable": false, + "type": "Data_Feed_TCP_Client", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "NO_POS_Mode_S", + "use_mode_ac": false + }, + "url": "192.168.208.153", + "port": 10005 + }, + { + "key": "Port_30003", + "enable": false, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "SBS", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true + }, + "port": 30003 + } + ] + }, + "data_source": { + "list": [ + { + "key": "mode_acs_shared_memory", + "enable": true, + "base_station_show": true, + "aircraft_show": false, + "color": "#e4f40b", + "aircraft_pixel_size": 38, + "lat": 37.073685, + "lon": 120.870472, + "alt": 9479.280000, + "update_form_gps": false, + "type": "Shared_Memory_Data_Source", + "shared_memory_name": "mode_acs", + "shared_memory_size": 20000, + "data_type": "BIN_Blank" + } + ] + }, + "source_feed_relation": { + "list": [ + { + "key": "key_10003", + "enable": true, + "type": "First_Source_To_All_Feed_Relation" + } + ] + } + }, + "log": { + "open_mode": "Delete", + "piece_num": 10, + "size_MB": 10 + }, + "console": { + "record_playback": false, + "mode_s_console": false, + "post_request": false, + "mlat_server": true + } +} diff --git a/config/S79hostapd b/config/S79hostapd new file mode 100644 index 0000000..db78b80 --- /dev/null +++ b/config/S79hostapd @@ -0,0 +1,25 @@ +#!/bin/sh + +[ -f /etc/hostapd.conf ] || exit 0 + +case "$1" in + start) + printf "Starting hostapd: " + start-stop-daemon -S -x /usr/sbin/hostapd -- -B /etc/hostapd.conf + [ $? = 0 ] && echo "OK" || echo "FAIL" + ;; + stop) + printf "Stopping hostapd: " + start-stop-daemon -K -q -x /usr/sbin/hostapd + [ $? = 0 ] && echo "OK" || echo "FAIL" + ;; + restart|reload) + $0 stop + $0 start + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 +esac + +exit 0 \ No newline at end of file diff --git a/config/S90E-CAP b/config/S90E-CAP new file mode 100644 index 0000000..938e691 --- /dev/null +++ b/config/S90E-CAP @@ -0,0 +1,26 @@ +#!/bin/sh + +case "$1" in + start|"") + while true + do + printf "Starting E-CAP: " + cd /root/ + ./Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug > out.log 2>&1 +# ./Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug + sleep 3 + done & + ;; + stop|status) + printf "Stopping E-CAP: " + pkill Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug + ;; + restart|reload|force-reload) + echo "Error: argument '$1' not supported" >&2 + exit 3 + ;; + *) + echo "Usage: [start|stop]" >&2 + exit 3 + ;; +esac diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..2f0232f --- /dev/null +++ b/config/config.json @@ -0,0 +1,302 @@ +{ + "version": "windows_v1.1.2", + "dsp": { + "library_path": "@/dll_source.dll", + "center_freq": 16000000, + "band_width": 6000, + "sample_rate": 60000, + "fft_point_number": 2048, + "fft_period_ms": 50, + "fft_win_type": "blkmc", + "url": "0.0.0.0", + "port": 6000, + "power_min": -380, + "power_max": 120, + "power_offset": -166, + "frame_rate": 60, + "debug": false, + "demod_mode": "LSB", + "audio_gain": 30, + "color_gain": 120 + }, + "drogon": { + "app": { + "threads_num": 0, + "client_max_body_size": "1000M", + "client_max_memory_body_size": "1000M" + } + }, + "http_monitor": { + "access_log": false, + "slow_request_threshold_ms": 3000, + "slow_scope_threshold_ms": 1000 + }, + "mlat": { + "enable": false, + "merge": false, + "library_path": "@/mlat_source.dll", + "function_name": "adsb_read" + }, + "web_server": { + "webapp": "D:/ae/proj/projects/ECAP_Server/ver/v1.0.4/wwwroot", + "tiles": "D:/tiles" + }, + "device": { + "net": [ + { + "key": "device", + "ip": "192.168.1.75", + "netmask": "255.255.255.0", + "gateway": "192.168.1.1", + "port": 80 + }, + { + "key": "wifi", + "ip": "192.168.10.193", + "netmask": "255.255.255.0", + "gateway": "192.168.10.1", + "port": 80 + } + ] + }, + "mode_acs": { + "timeout_seconds": 160, + "max_speed_m_s": 1000, + "air_pos_timeout": 8, + "surface_pos_timeout": 32, + "read_milliseconds": 100, + "max_track_point_size": 20000, + "mode_s_max_num": 700000, + "mode_other_max_num": 10000, + "report_data_feed_msg_mum": 10000, + "default_min_aircraft_list_num": 10, + "ignore_df_11": true, + "monitor_msg_live": false, + "data_feed": { + "empty_wait_milliseconds": 30, + "packet_byte_size": 0, + "tcp_server_default_connect_user_buffer_size": 409600, + "tcp_server_default_connect_system_buffer_size": 409600, + "list": [ + { + "key": "Port_10003", + "enable": true, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "port": 10003, + "connect_user_buffer_size": 409600, + "connect_system_buffer_size": 409600 + }, + { + "key": "22333", + "enable": false, + "type": "Data_Feed_UDP_Server", + "output_format": { + "type": "BIN", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": false, + "sbs_only_pos": false + }, + "port": 50001 + }, + { + "key": "Port_10004", + "enable": false, + "type": "Data_Feed_UDP_Client", + "output_format": { + "type": "AVR", + "use_status": false, + "mode_s_output_type": "DF_11_17_18", + "use_mode_ac": false, + "sbs_only_pos": true + }, + "url": "192.168.1.75", + "port": 30004 + }, + { + "key": "Port_10005", + "enable": true, + "type": "Data_Feed_TCP_Client", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "NO_POS_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "url": "192.168.1.167", + "port": 10005 + }, + { + "key": "Port_30003", + "enable": false, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "SBS", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "port": 30003, + "connect_user_buffer_size": 0, + "connect_system_buffer_size": 0 + } + ] + }, + "data_source": { + "list": [ + { + "key": "lzy_dll", + "enable": true, + "base_station_show": true, + "aircraft_show": true, + "color": "#454641", + "aircraft_pixel_size": 28, + "type": "Dll_Data_Source", + "lat": 37.433547, + "lon": 121.408730, + "alt": 23.000000, + "update_form_gps": true, + "show_icao": true, + "show_call_sign": false, + "show_fly_status": false, + "keep_mode": true, + "library_path": "@/dll_source.dll", + "function_name": "adsb_read", + "data_type": "BIN_Blank_Text", + "buffer_size": 200000 + }, + { + "key": "aaaa", + "enable": false, + "base_station_show": true, + "aircraft_show": true, + "color": "#d1a54c", + "aircraft_pixel_size": 20, + "type": "File_Data_Source", + "lat": 37.000000, + "lon": 121.000000, + "alt": 0.000000, + "update_form_gps": false, + "show_icao": true, + "show_call_sign": true, + "show_fly_status": true, + "keep_mode": true, + "file_path": "D:\\ae\\proj\\projects\\ECAP_Server\\data\\BIN_Blank_Text\\ADS-B_195_0205.txt", + "data_type": "BIN_Blank_Text", + "play_mode": "loop" + } + ] + }, + "source_feed_relation": { + "list": [ + { + "key": "key_10003", + "enable": true, + "type": "First_Source_To_All_Feed_Relation" + } + ] + } + }, + "log": { + "open_mode": "Delete", + "piece_num": 10, + "size_MB": 10 + }, + "console": { + "record_playback": false, + "mode_s_console": false, + "post_request": false, + "mlat_server": true + }, + "external_database": { + "ecap_sqlite_path": "D:/ae/proj/projects/ECAP_Server/sqlite/ecap.sqlite", + "list": [ + { + "name": "tar1090_db_aircraft", + "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", + "description": "wiedehopf/tar1090-db 发布的轻量 ICAO24 飞机静态库,提供注册号、机型、年份和运营方备注。", + "update_interval_seconds": 604800, + "last_updated_at": 1780388281 + }, + { + "name": "wiedehopf_tar1090_db_aircraft", + "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", + "description": "wiedehopf/tar1090-db 项目发布的飞机静态数据库镜像,供 tar1090 本地识别飞机。", + "update_interval_seconds": 604800, + "last_updated_at": 1780381804 + }, + { + "name": "mictronics_aircraft_database", + "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", + "description": "Mictronics 社区飞机数据库的 tar1090 汇总镜像,可按 ICAO24 查询注册号、机型和运营方。", + "update_interval_seconds": 604800, + "last_updated_at": 1780381804 + }, + { + "name": "opensky_aircraft_database", + "download_url": "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv", + "description": "OpenSky Network 飞机元数据 CSV,包含制造商、型号、注册号和运营方等字段。", + "update_interval_seconds": 604800, + "last_updated_at": 1780381804 + }, + { + "name": "faa_aircraft_registry", + "download_url": "https://registry.faa.gov/database/ReleasableAircraft.zip", + "description": "FAA 官方可公开下载的美国飞机注册库,MASTER.txt 包含 Mode-S Hex 注册信息。", + "update_interval_seconds": 86400, + "last_updated_at": 1780381804 + }, + { + "name": "icao_doc_8643_aircraft_type_designators", + "download_url": "", + "description": "ICAO Doc 8643 机型代码表,用于将 A320、B738 等 designator 映射为制造商、型号和类别。", + "update_interval_seconds": 2419200, + "last_updated_at": 1780381804 + }, + { + "name": "vrs_routes", + "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", + "description": "VRS 航线表,以 callsign 查询机场代码序列,用于推断起飞机场、经停机场和目的机场。", + "update_interval_seconds": 3600, + "last_updated_at": 1780381804 + }, + { + "name": "vrs_airports", + "download_url": "https://vrs-standing-data.adsb.lol/airports.csv", + "description": "VRS 机场字典,提供机场代码、名称、国家、经纬度和高度。", + "update_interval_seconds": 3600, + "last_updated_at": 1780381804 + }, + { + "name": "adsblol_vrs_standing_data_routes", + "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", + "description": "ADSB.lol 发布的 VRS standing-data 航线镜像,以 callsign 查询机场代码序列。", + "update_interval_seconds": 3600, + "last_updated_at": 1780381804 + }, + { + "name": "vradarserver_standing_data_routes", + "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", + "description": "Virtual Radar Server standing-data 的航线适配表;当前在线更新使用 ADSB.lol 公共镜像。", + "update_interval_seconds": 3600, + "last_updated_at": 1780381804 + }, + { + "name": "opensky_flightdata_api_cache", + "download_url": "", + "description": "OpenSky FlightData API 查询缓存,按 ICAO24 和时间范围保存历史航班起降机场结果。", + "update_interval_seconds": 0, + "last_updated_at": 1780381804 + } + ] + } +} diff --git a/config/config.json.back b/config/config.json.back new file mode 100644 index 0000000..fdc8eb2 --- /dev/null +++ b/config/config.json.back @@ -0,0 +1,302 @@ +{ + "version": "windows_v1.1.2", + "dsp": { + "library_path": "@/dll_source.dll", + "center_freq": 16000000, + "band_width": 6000, + "sample_rate": 60000, + "fft_point_number": 2048, + "fft_period_ms": 50, + "fft_win_type": "blkmc", + "url": "0.0.0.0", + "port": 6000, + "power_min": -380, + "power_max": 120, + "power_offset": -166, + "frame_rate": 60, + "debug": false, + "demod_mode": "LSB", + "audio_gain": 30, + "color_gain": 120 + }, + "drogon": { + "app": { + "threads_num": 0, + "client_max_body_size": "1000M", + "client_max_memory_body_size": "1000M" + } + }, + "http_monitor": { + "access_log": true, + "slow_request_threshold_ms": 1000, + "slow_scope_threshold_ms": 100 + }, + "mlat": { + "enable": false, + "merge": false, + "library_path": "@/mlat_source.dll", + "function_name": "adsb_read" + }, + "web_server": { + "webapp": "D:/ae/proj/projects/ECAP_Server/ver/v1.0.4/wwwroot", + "tiles": "D:/tiles" + }, + "device": { + "net": [ + { + "key": "device", + "ip": "192.168.1.75", + "netmask": "255.255.255.0", + "gateway": "192.168.1.1", + "port": 80 + }, + { + "key": "wifi", + "ip": "192.168.10.193", + "netmask": "255.255.255.0", + "gateway": "192.168.10.1", + "port": 80 + } + ] + }, + "mode_acs": { + "timeout_seconds": 160, + "max_speed_m_s": 1000, + "air_pos_timeout": 8, + "surface_pos_timeout": 32, + "read_milliseconds": 100, + "max_track_point_size": 20000, + "mode_s_max_num": 700000, + "mode_other_max_num": 10000, + "report_data_feed_msg_mum": 10000, + "default_min_aircraft_list_num": 10, + "ignore_df_11": true, + "monitor_msg_live": false, + "ecap_sqlite_path": "D:/ae/proj/projects/ECAP_Server/sqlite/ecap.sqlite", + "data_feed": { + "empty_wait_milliseconds": 30, + "packet_byte_size": 0, + "tcp_server_default_connect_user_buffer_size": 409600, + "tcp_server_default_connect_system_buffer_size": 409600, + "list": [ + { + "key": "Port_10003", + "enable": true, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "port": 10003, + "connect_user_buffer_size": 409600, + "connect_system_buffer_size": 409600 + }, + { + "key": "22333", + "enable": false, + "type": "Data_Feed_UDP_Server", + "output_format": { + "type": "BIN", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": false, + "sbs_only_pos": false + }, + "port": 50001 + }, + { + "key": "Port_10004", + "enable": false, + "type": "Data_Feed_UDP_Client", + "output_format": { + "type": "AVR", + "use_status": false, + "mode_s_output_type": "DF_11_17_18", + "use_mode_ac": false, + "sbs_only_pos": true + }, + "url": "192.168.1.75", + "port": 30004 + }, + { + "key": "Port_10005", + "enable": true, + "type": "Data_Feed_TCP_Client", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "NO_POS_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "url": "192.168.1.167", + "port": 10005 + }, + { + "key": "Port_30003", + "enable": false, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "SBS", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "port": 30003, + "connect_user_buffer_size": 0, + "connect_system_buffer_size": 0 + } + ] + }, + "data_source": { + "list": [ + { + "key": "aaaa", + "enable": false, + "base_station_show": true, + "aircraft_show": true, + "color": "#d1a54c", + "aircraft_pixel_size": 20, + "type": "File_Data_Source", + "lat": 37.000000, + "lon": 121.000000, + "alt": 0.000000, + "update_form_gps": false, + "show_icao": true, + "show_call_sign": true, + "show_fly_status": true, + "keep_mode": true, + "file_path": "D:\\ae\\proj\\projects\\ECAP_Server\\data\\BIN_Blank_Text\\ADS-B_195_0205.txt", + "data_type": "BIN_Blank_Text", + "play_mode": "loop" + }, + { + "key": "lzy_dll", + "enable": true, + "base_station_show": true, + "aircraft_show": true, + "color": "#454641", + "aircraft_pixel_size": 28, + "type": "Dll_Data_Source", + "lat": 37.433547, + "lon": 121.408730, + "alt": 23.000000, + "update_form_gps": true, + "show_icao": true, + "show_call_sign": false, + "show_fly_status": false, + "keep_mode": true, + "library_path": "@/dll_source.dll", + "function_name": "adsb_read", + "data_type": "BIN_Blank_Text", + "buffer_size": 200000 + } + ] + }, + "source_feed_relation": { + "list": [ + { + "key": "key_10003", + "enable": true, + "type": "First_Source_To_All_Feed_Relation" + } + ] + } + }, + "log": { + "open_mode": "Delete", + "piece_num": 10, + "size_MB": 10 + }, + "console": { + "record_playback": false, + "mode_s_console": false, + "post_request": false, + "mlat_server": true + }, + "external_database": { + "list": [ + { + "name": "tar1090_db_aircraft", + "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", + "description": "wiedehopf/tar1090-db 发布的轻量 ICAO24 飞机静态库,提供注册号、机型、年份和运营方备注。", + "update_interval_seconds": 604800, + "last_updated_at": 0 + }, + { + "name": "wiedehopf_tar1090_db_aircraft", + "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", + "description": "wiedehopf/tar1090-db 项目发布的飞机静态数据库镜像,供 tar1090 本地识别飞机。", + "update_interval_seconds": 604800, + "last_updated_at": 0 + }, + { + "name": "mictronics_aircraft_database", + "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", + "description": "Mictronics 社区飞机数据库的 tar1090 汇总镜像,可按 ICAO24 查询注册号、机型和运营方。", + "update_interval_seconds": 604800, + "last_updated_at": 0 + }, + { + "name": "opensky_aircraft_database", + "download_url": "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv", + "description": "OpenSky Network 飞机元数据 CSV,包含制造商、型号、注册号和运营方等字段。", + "update_interval_seconds": 604800, + "last_updated_at": 0 + }, + { + "name": "faa_aircraft_registry", + "download_url": "https://registry.faa.gov/database/ReleasableAircraft.zip", + "description": "FAA 官方可公开下载的美国飞机注册库,MASTER.txt 包含 Mode-S Hex 注册信息。", + "update_interval_seconds": 86400, + "last_updated_at": 0 + }, + { + "name": "icao_doc_8643_aircraft_type_designators", + "download_url": "", + "description": "ICAO Doc 8643 机型代码表,用于将 A320、B738 等 designator 映射为制造商、型号和类别。", + "update_interval_seconds": 2419200, + "last_updated_at": 0 + }, + { + "name": "vrs_routes", + "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", + "description": "VRS 航线表,以 callsign 查询机场代码序列,用于推断起飞机场、经停机场和目的机场。", + "update_interval_seconds": 3600, + "last_updated_at": 0 + }, + { + "name": "vrs_airports", + "download_url": "https://vrs-standing-data.adsb.lol/airports.csv", + "description": "VRS 机场字典,提供机场代码、名称、国家、经纬度和高度。", + "update_interval_seconds": 3600, + "last_updated_at": 0 + }, + { + "name": "adsblol_vrs_standing_data_routes", + "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", + "description": "ADSB.lol 发布的 VRS standing-data 航线镜像,以 callsign 查询机场代码序列。", + "update_interval_seconds": 3600, + "last_updated_at": 0 + }, + { + "name": "vradarserver_standing_data_routes", + "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", + "description": "Virtual Radar Server standing-data 的航线适配表;当前在线更新使用 ADSB.lol 公共镜像。", + "update_interval_seconds": 3600, + "last_updated_at": 0 + }, + { + "name": "opensky_flightdata_api_cache", + "download_url": "", + "description": "OpenSky FlightData API 查询缓存,按 ICAO24 和时间范围保存历史航班起降机场结果。", + "update_interval_seconds": 0, + "last_updated_at": 0 + } + ] + } +} diff --git a/config/config2.json b/config/config2.json new file mode 100644 index 0000000..0b2913e --- /dev/null +++ b/config/config2.json @@ -0,0 +1,251 @@ +{ + "version": "v1.0.4", + "dsp": { + "library_path": "@/dll_source.dll", + "center_freq": 16000000, + "band_width": 6000, + "sample_rate": 60000, + "fft_point_number": 2048, + "fft_period_ms": 50, + "fft_win_type": "blkmc", + "url": "0.0.0.0", + "port": 6000, + "power_min": -380, + "power_max": 120, + "power_offset": -166, + "frame_rate": 60, + "debug": false, + "demod_mode": "LSB", + "audio_gain": 30, + "color_gain": 120 + }, + "drogon": { + "app": { + "threads_num": 0, + "client_max_body_size": "1000M", + "client_max_memory_body_size": "1000M" + } + }, + "mlat": { + "enable": false, + "merge": false, + "library_path": "@/mlat_source.dll", + "function_name": "adsb_read" + }, + "web_server": { + "webapp": "@/wwwroot", + "tiles": "D:/tiles" + }, + "device": { + "net": [ + { + "key": "device", + "ip": "192.168.1.75", + "netmask": "255.255.255.0", + "gateway": "192.168.1.1", + "port": 80 + }, + { + "key": "wifi", + "ip": "192.168.10.193", + "netmask": "255.255.255.0", + "gateway": "192.168.10.1", + "port": 80 + } + ] + }, + "mode_acs": { + "timeout_seconds": 160, + "max_speed_m_s": 1000, + "air_pos_timeout": 8, + "surface_pos_timeout": 32, + "read_milliseconds": 100, + "max_track_point_size": 20000, + "mode_s_max_num": 700000, + "mode_other_max_num": 10000, + "report_data_feed_msg_mum": 10000, + "default_min_aircraft_list_num": 10, + "ignore_df_11": true, + "monitor_msg_live": false, + "ecap_sqlite_path": "D:/ae/proj/projects/ECAP_Server/sqlite/ecap.sqlite", + "data_feed": { + "empty_wait_milliseconds": 30, + "packet_byte_size": 0, + "tcp_server_default_connect_user_buffer_size": 0, + "tcp_server_default_connect_system_buffer_size": 0, + "list": [ + { + "key": "Port_10003", + "enable": true, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "port": 10003, + "connect_user_buffer_size": 409600, + "connect_system_buffer_size": 409600 + }, + { + "key": "22333", + "enable": false, + "type": "Data_Feed_UDP_Server", + "output_format": { + "type": "BIN", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": false, + "sbs_only_pos": false + }, + "port": 50001 + }, + { + "key": "Port_10004", + "enable": false, + "type": "Data_Feed_UDP_Client", + "output_format": { + "type": "AVR", + "use_status": false, + "mode_s_output_type": "DF_11_17_18", + "use_mode_ac": false, + "sbs_only_pos": true + }, + "url": "192.168.1.75", + "port": 30004 + }, + { + "key": "Port_10005", + "enable": true, + "type": "Data_Feed_TCP_Client", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "NO_POS_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "url": "192.168.1.75", + "port": 10005 + }, + { + "key": "Port_30003", + "enable": false, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "SBS", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "port": 30003, + "connect_user_buffer_size": 0, + "connect_system_buffer_size": 0 + } + ] + }, + "data_source": { + "list": [ + { + "key": "aaaa", + "enable": true, + "base_station_show": true, + "aircraft_show": true, + "color": "#d1a54c", + "aircraft_pixel_size": 20, + "lat": 37.000000, + "lon": 121.000000, + "alt": 0.000000, + "update_form_gps": false, + "type": "File_Data_Source", + "file_path": "D:\\ae\\proj\\projects\\ECAP_Server\\data\\BIN_Blank_Text\\ADS-B_195_0205.txt", + "data_type": "BIN_Blank_Text", + "play_mode": "one" + }, + { + "key": "本地radarcape", + "enable": true, + "base_station_show": true, + "aircraft_show": true, + "color": "#cb3065", + "aircraft_pixel_size": 20, + "lat": 37.000000, + "lon": 121.000000, + "alt": 0.000000, + "update_form_gps": false, + "type": "TCP_Client_Data_Source", + "ip": "192.168.71.101", + "port": 10003 + }, + { + "key": "r205_113", + "enable": false, + "base_station_show": false, + "aircraft_show": true, + "color": "#661a30", + "aircraft_pixel_size": 30, + "lat": 37.000000, + "lon": 121.000000, + "alt": 0.000000, + "update_form_gps": false, + "type": "TCP_Client_Data_Source", + "ip": "192.168.205.113", + "port": 10003 + }, + { + "key": "102", + "enable": true, + "base_station_show": true, + "aircraft_show": true, + "color": "#186ce1", + "aircraft_pixel_size": 20, + "lat": 0.000000, + "lon": 0.000000, + "alt": 0.000000, + "update_form_gps": false, + "type": "TCP_Client_Data_Source", + "ip": "192.168.1.102", + "port": 10003 + }, + { + "key": "r205_116", + "enable": false, + "base_station_show": true, + "aircraft_show": true, + "color": "#d7c1b6a3", + "aircraft_pixel_size": 40, + "lat": 0.000000, + "lon": 0.000000, + "alt": 5394.960000, + "update_form_gps": true, + "type": "TCP_Client_Data_Source", + "ip": "192.168.205.116", + "port": 10003 + } + ] + }, + "source_feed_relation": { + "list": [ + { + "key": "key_10003", + "enable": true, + "type": "First_Source_To_All_Feed_Relation" + } + ] + } + }, + "log": { + "open_mode": "Delete", + "piece_num": 10, + "size_MB": 10 + }, + "console": { + "record_playback": false, + "mode_s_console": false, + "post_request": false, + "mlat_server": true + } +} diff --git a/config/data_progress_config.json b/config/data_progress_config.json new file mode 100644 index 0000000..e84b68b --- /dev/null +++ b/config/data_progress_config.json @@ -0,0 +1,9 @@ +{ + "library_path": "@/dll_source.dll", + "center_freq": 200000, + "band_width": 6000, + "sample_rate": 60000, + "fft_point_number": 2048, + "fft_period_ms": 50, + "fft_win_type": "blkmc" +} \ No newline at end of file diff --git a/config/deploy.sh b/config/deploy.sh new file mode 100644 index 0000000..9313523 --- /dev/null +++ b/config/deploy.sh @@ -0,0 +1,205 @@ +#!/bin/sh + +# 检查参数数量 +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +dir_path="/root/u" + +# 检查U盘目录是否存在 +if [ ! -d "$dir_path" ]; then + echo "U盘目录不存在,正在创建U盘目录..." + mkdir -p "$dir_path" # 创建目录 +else + echo "U盘目录已经存在." +fi + + +# 获取命令行参数 +sequence_number=$1 + +# 定义一个函数来执行复制操作并输出结果 +copy_file() { + local source_file="$1" + local target_dir="$2" + if [ -f "$source_file" ]; then + cp "$source_file" "$target_dir" + if [ $? -eq 0 ]; then + echo "文件 $source_file 已成功复制到 $target_dir 目录。" + else + echo "复制文件 $source_file 时出错。" + exit 1 + fi + else + echo "当前目录下未找到 $source_file 文件。" + fi +} +# 添加 启动脚本 到 /etc/init.d/ +copy_file "S79hostapd" "/etc/init.d/" +copy_file "S90E-CAP" "/etc/init.d/" +#copy_file "S99Check_reboot" "/etc/init.d/" + +# 移除 启动脚本 +rename_file() { + local original_name="$1" + local new_name="$2" + + if [ -f "$original_name" ]; then + mv "$original_name" "$new_name" + if [ $? -eq 0 ]; then + echo "已成功将 $original_name 重命名为 $new_name" + else + echo "重命名 $original_name 时出错。" + exit 1 + fi + else + echo "$original_name 文件不存在" + fi +} +rename_file "/etc/init.d/S49weston" "/etc/init.d/_S49weston" +rename_file "/etc/init.d/S41dhcpcd" "/etc/init.d/_S41dhcpcd" + +# 生成 /etc/hostapd.conf 文件 +hostapd_config="# 无线网络接口名称,可通过 ifconfig 或 ip addr 命令查看 +interface=wlan0 +# 使用的驱动程序,通常为 nl80211 +driver=nl80211 +# 热点的 SSID +ssid=E-CAP_${sequence_number} +# 通道号,可根据需要调整 +channel=6 +# 认证和加密模式 +hw_mode=g +ieee80211n=1 +wmm_enabled=1 +ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40] +macaddr_acl=0 +auth_algs=1 +ignore_broadcast_ssid=0 +wpa=2 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=TKIP +rsn_pairwise=CCMP +# 热点的密码 +wpa_passphrase=12345678" + +echo "$hostapd_config" > /etc/hostapd.conf +if [ $? -eq 0 ]; then + echo "文件 /etc/hostapd.conf 已成功生成。" +else + echo "生成 /etc/hostapd.conf 文件时出错。" + exit 1 +fi + +# 生成 /etc/dnsmasq.conf 文件 +dnsmasq_config="# 监听的接口 +interface=wlan0 +# DHCP 服务的 IP 地址范围 +dhcp-range=192.168.10.10,192.168.10.100,255.255.255.0,12h +# 网关地址 +dhcp-option=3,192.168.10.1 +# DNS 服务器地址 +dhcp-option=6,8.8.8.8 +# 本地 DNS 服务器地址 +server=8.8.8.8 +# 禁用 DNS 缓存 +no-hosts +# 不读取 /etc/resolv.conf 文件 +no-resolv" + +echo "$dnsmasq_config" > /etc/dnsmasq.conf +if [ $? -eq 0 ]; then + echo "文件 /etc/dnsmasq.conf 已成功生成。" +else + echo "生成 /etc/dnsmasq.conf 文件时出错。" + exit 1 +fi + +# 生成 /etc/network/interfaces 文件 +network_config="# interface file auto-generated by buildroot + +auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address 192.168.1.${sequence_number} + netmask 255.255.255.0 + gateway 192.168.1.1 + dns-nameservers 192.168.1.1 8.8.8.8 + post-up /usr/sbin/ethtool -s eth0 speed 100 duplex full autoneg off + pre-down /usr/sbin/ethtool -s eth0 autoneg on +# post-up 网络接口被激活之后应该执行的命令。 +# pre-down 在网络接口即将被关闭之前执行的命令。 +# 添加这两行命令是为了限制网速为100M,解决phy网络排线过长导致网卡不稳定的问题。 + +auto wlan0 +iface wlan0 inet static + address 192.168.10.${sequence_number} + netmask 255.255.255.0 + gateway 192.168.10.1 + dns-nameservers 192.168.10.1 8.8.8.8" + +# 生成 /etc/network/interfaces 文件 +echo "$network_config" > /etc/network/interfaces +if [ $? -eq 0 ]; then + echo "文件 /etc/network/interfaces 已成功生成。" +else + echo "生成 /etc/network/interfaces 文件时出错。" + exit 1 +fi + + +if [ ! -d "/root/tiles" ]; then + if [ -f "tiles.zip" ]; then # 检查 tiles.zip 文件是否存在 + unzip -q "tiles.zip" -d "/root/" # 解压 tiles.zip 到 /root/ 目录 + if [ $? -eq 0 ]; then + echo "tiles.zip 已成功解压到 /root/ 目录。" + else + echo "解压 tiles.zip 到 /root/ 目录时出错。" + exit 1 + fi + else + echo "当前目录下未找到 tiles.zip 文件。" + fi +else + echo "瓦片文件夹已存在。" +fi + + + +# 检查当前目录下的 Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug 文件是否存在 +if [ -f "Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug" ]; then + # 给文件添加执行权限 + chmod +x "Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug" + if [ $? -eq 0 ]; then + echo "已成功给当前目录下的 Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug 文件添加执行权限。" + else + echo "给当前目录下的 Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug 文件添加执行权限时出错。" + exit 1 + fi +else + echo "当前目录下未找到 Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug 文件。" +fi + +# 定义要设置的 root 密码 +ROOT_PASSWORD="123" + +# 使用 expect 来非交互式地修改 root 密码 +expect << EOF +spawn passwd root +expect "New password:" +send "$ROOT_PASSWORD\r" +expect "Retype new password:" +send "$ROOT_PASSWORD\r" +expect eof +EOF + +if [ $? -eq 0 ]; then + echo "root 用户密码已成功设置。" +else + echo "设置 root 用户密码时出错。" + exit 1 +fi \ No newline at end of file diff --git a/config/interface b/config/interface new file mode 100644 index 0000000..577f6b3 --- /dev/null +++ b/config/interface @@ -0,0 +1,36 @@ + + + +# interface file auto-generated by buildroot + +auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address ${ip} + netmask ${netmask} + gateway ${gateway} + dns-nameservers ${gateway} 8.8.8.8 + post-up /usr/sbin/ethtool -s eth0 speed 100 duplex full autoneg off + pre-down /usr/sbin/ethtool -s eth0 autoneg on +# post-up 网络接口被激活之后应该执行的命令。 +# pre-down 在网络接口即将被关闭之前执行的命令。 +# 添加这两行命令是为了限制网速为100M,解决phy网络排线过长导致网卡不稳定的问题。 + +auto wlan0 +iface wlan0 inet static + address ${wifi_ip} + netmask ${wifi_netmask} + gateway ${wifi_gateway} + dns-nameservers ${wifi_gateway} 8.8.8.8 + + +auto eth75 +iface eth75 inet static + address 192.168.59.75 + netmask 255.255.255.0 + gateway 192.168.59.1 + dns-nameservers 192.168.59.1 8.8.8.8 + post-up /usr/sbin/ethtool -s eth75 speed 100 duplex full autoneg off + pre-down /usr/sbin/ethtool -s eth75 autoneg on \ No newline at end of file diff --git a/config/polkit 规则 b/config/polkit 规则 new file mode 100644 index 0000000..6e049c3 --- /dev/null +++ b/config/polkit 规则 @@ -0,0 +1,20 @@ +sudo nano /etc/polkit-1/rules.d/90-ecap.rules +polkit.addRule(function(action, subject) { + if ( + subject.user == "heco" && + action.id == "org.freedesktop.systemd1.manage-units" && + action.lookup("unit") == "ecap.service" + ) { + return polkit.Result.YES; + } +}); + +sudo mkdir /etc/polkit-1/rules.d +sudo chmod 777 /etc/polkit-1/rules.d +sudo touch /etc/polkit-1/rules.d/90-ecap.rules +sudo chmod 777 /etc/polkit-1/rules.d/90-ecap.rules + +sudo systemctl restart polkit +systemctl stop ecap +systemctl start ecap +systemctl restart ecap \ No newline at end of file diff --git a/config/pt b/config/pt new file mode 100644 index 0000000..528ff99 --- /dev/null +++ b/config/pt @@ -0,0 +1,226 @@ +#!/bin/bash + + +# 日志轮换函数 +rotate_log() { + local LOG_FILE="$1" # 日志文件路径 + local MAX_SIZE="$2" # 最大日志文件大小(如 50M) + local MAX_BACKUPS="$3" # 保留的最大备份数量 + + # 如果日志文件是 /dev/null,直接返回,不进行轮换 + if [ "$LOG_FILE" == "/dev/null" ]; then + echo "日志文件是 /dev/null,跳过日志轮换" + return + fi + + local LOG_DIR=$(dirname "$LOG_FILE") # 日志文件所在的目录 + local DATE_FORMAT=$(date +"%Y%m%d-%H%M%S") # 当前日期和时间格式 + + # 检查日志文件是否存在 + if [ ! -f "$LOG_FILE" ]; then + echo "日志文件不存在,创建新日志文件: $LOG_FILE" + touch "$LOG_FILE" + fi + + # 检查日志文件的大小 + log_size=$(stat -c %s "$LOG_FILE") # 获取日志文件的大小(字节) + max_size_bytes=$(numfmt --from=iec $MAX_SIZE) # 转换最大大小为字节 + + # 如果日志文件大小超过限制,进行轮换 + if [ "$log_size" -ge "$max_size_bytes" ]; then + echo "日志文件超过最大限制 $MAX_SIZE,开始轮换日志" + + # 创建备份目录(如果没有的话) + mkdir -p "$LOG_DIR/backups" + + # 获取备份文件名 + backup_file="${LOG_DIR}/backups/$(basename "$LOG_FILE").${DATE_FORMAT}" + +# # 备份当前日志文件 +# mv "$LOG_FILE" "$backup_file" +# echo "备份日志为: $backup_file" +# +# # 创建一个新的空日志文件 +# touch "$LOG_FILE" + + # 复制当前日志作为备份(不动 inode) + cp "$LOG_FILE" "$backup_file" + echo "备份日志为: $backup_file" + + # 截断原日志文件(FD 不变) + : > "$LOG_FILE" + + # 压缩备份日志文件 + gzip "$backup_file" + echo "压缩备份日志为: ${backup_file}.gz" + + # 删除多余的备份文件(如果超过最大保留数量) + backups_count=$(ls "$LOG_DIR/backups" | grep -E "^$(basename "$LOG_FILE")" | wc -l) + if [ "$backups_count" -gt "$MAX_BACKUPS" ]; then + # 删除最早的备份 + oldest_backup=$(ls -t "$LOG_DIR/backups" | grep -E "^$(basename "$LOG_FILE")" | tail -n 1) + rm "${LOG_DIR}/backups/$oldest_backup" + echo "删除最早的备份日志: ${LOG_DIR}/backups/$oldest_backup" + fi + fi +} + + +DIR="$(cd "$(dirname "$0")" && pwd)" +BOOT_TIME=$(uptime -s) # 使用 uptime 命令获取系统的开机时间 +MAX_SIZE="1M" # 最大日志文件大小 +MAX_BACKUPS=2 # 保留的最大备份数量 +# 自动崩溃收集服务 +#sudo systemctl stop apport.service +#sudo systemctl disable apport.service +#ulimit -c unlimited +#echo "/home/heco/ys_ecap_server/core/core_%e_${BOOT_TIME}_.%t.%p" | tee /proc/sys/kernel/core_pattern + +USE_VALGRIND=0 +USE_data_process=0 + +NAME1="data_process" +NAME2="ecap_server" +#LOG_PATH1="/dev/null" +#LOG_PATH2="/dev/null" +LOG_PATH1="$DIR/data_process.log" +LOG_PATH2="$DIR/ecap_server.log" + +if [ "$USE_data_process" == 1 ]; then + rotate_log "$LOG_PATH1" "$MAX_SIZE" "$MAX_BACKUPS" +fi + +rotate_log "$LOG_PATH2" "$MAX_SIZE" "$MAX_BACKUPS" + + + + + +# Ensure the log files exist and are writable +if [ "$USE_data_process" == 1 ]; then + echo "系统开机时间: $BOOT_TIME" >> "${LOG_PATH1}" + touch "${LOG_PATH1}" +fi + +echo "系统开机时间: $BOOT_TIME" >> "${LOG_PATH2}" +touch "${LOG_PATH2}" +echo "${DIR} 创建log文件成功" + + +if [ "$USE_VALGRIND" == 1 ]; then + echo "启用valgrind内存检查程序" + valgrind --leak-check=full --log-file=${DIR}/${NAME2}_valgrind.log --show-leak-kinds=all ${DIR}/${NAME2} >> "${LOG_PATH2}" 2>&1 & +fi + + +SLEEP_SEC="${1:-10}" +sleep "$SLEEP_SEC" +echo "等待 ${SLEEP_SEC}s 完成" + + + +if [ "$USE_data_process" == 1 ]; then + echo "第一次启动 ${NAME1}" + ${DIR}/${NAME1} >> "${LOG_PATH1}" 2>&1 & + if [ $? -ne 0 ]; then + echo "第一次启动 Failed to start ${NAME1} at $(date)" >> "${LOG_PATH1}" + fi +fi + + +restart_count_2=0 +echo "第一次启动 ${NAME2}" +${DIR}/${NAME2} >> "${LOG_PATH2}" 2>&1 & +if [ $? -ne 0 ]; then + echo "第一次启动 Failed to start ${NAME2} at $(date)" >> "${LOG_PATH2}" +fi + + +while true; do + # 监控 data_process + + if [ "$USE_data_process" == 1 ]; then + if ! pgrep -x "$NAME1" >/dev/null; then + restart_count_1=$((restart_count_1 + 1)) + echo "重启 ${NAME1} at $(date) - count: ${restart_count_1}" >> "${LOG_PATH1}" + echo "重启 ${NAME1} at $(date) - count: ${restart_count_1}" + + # 真正重启程序 + ${DIR}/${NAME1} >> "${LOG_PATH1}" 2>&1 & + if [ $? -ne 0 ]; then + echo "Failed to start ${NAME1} at $(date)" >> "${LOG_PATH1}" + fi + fi + fi + + sleep 60 + + # 监控 ecap_server + if [ "$USE_VALGRIND" == 0 ]; then + if ! pgrep -x "$NAME2" >/dev/null; then + + ss -ltnp >> "${LOG_PATH2}" + restart_count_2=$((restart_count_2 + 1)) + echo "重启 ${NAME2} at $(date) - count: ${restart_count_2}" >> "${LOG_PATH2}" + echo "重启 ${NAME2} at $(date) - count: ${restart_count_2}" + # 真正启动 + ${DIR}/${NAME2} >> "${LOG_PATH2}" 2>&1 & + if [ $? -ne 0 ]; then + echo "Failed to start ${NAME2} at $(date)" >> "${LOG_PATH2}" + fi + fi + fi + + + + + if [ "$USE_data_process" == 1 ]; then + rotate_log "$LOG_PATH1" "$MAX_SIZE" "$MAX_BACKUPS" + fi + + rotate_log "$LOG_PATH2" "$MAX_SIZE" "$MAX_BACKUPS" + +done + + + + +# tail -f /home/heco/ys_ecap_server/ecap_1201/data_process.log +# tail -f /home/heco/ys_ecap_server/ecap_1201/ecap_server.log +# multitail /home/heco/ys_ecap_server/ecap_1201/data_process.log /home/heco/ys_ecap_server/ecap_1201/ecap_server.log + + + +# kill -SIGINT +# pkill -SIGINT +# pkill -SIGINT 1125ecap_server +# pgrep -l 1125ecap_server +# [ubuntu创建守护进程,并设置开机自启动(rc.local,rc_local.sercive)] https://blog.csdn.net/weixin_46954054/article/details/134862013 +# [Ubuntu开机自启动脚本/命令的几种方案] https://blog.csdn.net/weixin_43455581/article/details/107815743 + +# sudo pkill protect.bash && sudo pkill -SIGINT ecap_server && pgrep -l protect.bash + + +# pgrep -l protect.bash +# sudo pkill protect.bash +# sudo pkill -SIGINT ecap_server + +# /home/heco/ys_ecap_server/protect.bash ecap_server + +# kill -SIGINT +# pkill -SIGINT +# pkill -SIGINT 1125ecap_server +# pgrep -l 1125ecap_server +# [ubuntu创建守护进程,并设置开机自启动(rc.local,rc_local.sercive)] https://blog.csdn.net/weixin_46954054/article/details/134862013 +# [Ubuntu开机自启动脚本/命令的几种方案] https://blog.csdn.net/weixin_43455581/article/details/107815743 + +# pgrep -l protect.bash +# pkill protect.bash + +# /home/heco/ys_ecap_server/protect.bash ecap_server + +# sudo systemctl daemon-reload +# sudo systemctl enable rc-local.service # 设置开机自启 +# sudo systemctl disable rc-local.service # 关闭开机自启 +# sudo systemctl start rc-local.service # 立即启动服务 +# sudo systemctl status rc-local.service diff --git a/config/restart_net_service.sh b/config/restart_net_service.sh new file mode 100644 index 0000000..e1186e7 --- /dev/null +++ b/config/restart_net_service.sh @@ -0,0 +1,12 @@ +#!/bin/sh + + +reboot + + +#cat etc/network/interfaces +# +#ip link set dev eth0 down +#ip link set dev eth0 up + +#sudo systemctl restart networking \ No newline at end of file diff --git a/config/server_config_win.json b/config/server_config_win.json new file mode 100644 index 0000000..b23b38f --- /dev/null +++ b/config/server_config_win.json @@ -0,0 +1,25 @@ +{ + "file_name": "./qingdao/data", + "http_server": { + "ip": "0.0.0.0", + "port": 40000 + }, + "tcp_server": { + "ip": "0.0.0.0", + "port": 40001, + "clients": [ + { + "id": 0, + "ip": "0.0.0.0", + "port": 10003, + "info": "仿制radarcape" + }, + { + "id": 0, + "ip": "0.0.0.0", + "port": 10003, + "info": "radarcape" + } + ] + } +} \ No newline at end of file diff --git a/config/update.sh b/config/update.sh new file mode 100644 index 0000000..4096b0a --- /dev/null +++ b/config/update.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +export u_path="/media/udisk0" +export exe_name="Radarcape_Local_Server_aarch64-buildroot-linux-gnu-gxx_Linux_Debug" +export guard_exe_name="/bin/sh /etc/init.d/S90E-CAP start" + + +echo "pkill !" +pkill -f "${guard_exe_name}" +pkill -f "${exe_name}" + +echo "remove logs !" +rm -rf /root/logs* + +echo "update config.json!" +cp -r "$u_path"/config.json /root +echo "update restart_net_service.sh!" +cp -r "$u_path"/restart_net_service.sh /root +echo "update interface!" +cp -r "$u_path"/interface /root +echo "update wwwroot!" +rm -rf /root/wwwroot +cp -rp "$u_path"/wwwroot /root + + + +while pgrep -f "${guard_exe_name}" >/dev/null; do + echo "${guard_exe_name} Process is still running wait one second!" + sleep 1 +done +while pgrep -f "${exe_name}" >/dev/null; do + echo "${exe_name} Process is running still running wait one second!" + sleep 1 +done + + +echo "update ${exe_name}!" +cp -r "$u_path/${exe_name}" /root + +#/bin/sh /etc/init.d/S90E-CAP start + +echo "程序更新成功!" + +# 查看挂载: mount +# 卸载U盘: umount /media/udisk0 +# U盘默认地址:/media/udisk0 +# 单独更新 cp -r /media/udisk0/* /root +# mkdir /root/u && chmod 777 /root/u +# 自动部署: cp /media/udisk0/deploy.sh /root/u && chmod 777 /root/u/deploy.sh && /root/u/deploy.sh 190 +# 更新: cp /media/udisk0/update.sh /root/u && chmod 777 /root/u/update.sh && /root/u/update.sh + +# sudo fdisk -l + +# /etc/init.d/S90E-CAP stop +# pkill diff --git a/config/upgrade.bash b/config/upgrade.bash new file mode 100644 index 0000000..3a973da --- /dev/null +++ b/config/upgrade.bash @@ -0,0 +1,113 @@ +#!/bin/bash + +set -euo pipefail + + +if [[ "${UPGRADE_DAEMONIZED:-0}" != "1" ]]; then + export UPGRADE_DAEMONIZED=1 + nohup "$0" "$@" >/dev/null 2>&1 & + exit 0 +fi + +# ---------- 参数校验 ---------- +if [ "$#" -ne 2 ]; then + echo "用法: $0 " + exit 1 +fi + +old_version="$1" +new_version="$2" + +# ---------- 脚本目录 ---------- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# ---------- 日志 ---------- +LOG_DIR="${SCRIPT_DIR}/upgrade_logs" +LOG_FILE="${LOG_DIR}/upgrade_${old_version}_to_${new_version}.log" +mkdir -p "$LOG_DIR" + +# 覆盖写日志(重要) +: > "$LOG_FILE" + +# 所有输出写日志 + 控制台 +exec > >(tee -a "$LOG_FILE") 2>&1 + +echo "===== Upgrade start: $(date) =====" +echo "old_version=${old_version}" +echo "new_version=${new_version}" +echo "SCRIPT_DIR=${SCRIPT_DIR}" + +# ---------- 路径定义 ---------- +OLD_DIR="${SCRIPT_DIR}/${old_version}" +NEW_DIR="${SCRIPT_DIR}/${new_version}" +PKG="${NEW_DIR}/${new_version}.zip" +STOP_SCRIPT="${OLD_DIR}/stop.bash" +PT_BIN="${NEW_DIR}/pt" +PROTECT_SCRIPT="${SCRIPT_DIR}/protect.bash" + +# ---------- 基础校验 ---------- +for d in "$OLD_DIR" "$NEW_DIR"; do + [ -d "$d" ] || { echo "目录不存在: $d"; exit 1; } +done + +[ -f "$PKG" ] || { echo "升级包不存在: $PKG"; exit 1; } + +command -v unzip >/dev/null || { echo "unzip 未安装"; exit 1; } + +# ---------- 停止旧版本 ---------- +echo "[1/5] 停止旧版本" +if [ -x "$STOP_SCRIPT" ]; then + "$STOP_SCRIPT" +else + echo "stop.bash 不存在或不可执行: $STOP_SCRIPT" + exit 1 +fi + +# ---------- 解压新版本(覆盖) ---------- +echo "[2/5] 解压新版本" +unzip -o "$PKG" -d "$NEW_DIR" + +# ---------- 校验新程序 ---------- +echo "[3/5] 校验新程序" +[ -f "$PT_BIN" ] || { echo "pt 程序不存在: $PT_BIN"; exit 1; } +chmod +x "$PT_BIN" + +# ---------- 写 protect.bash(覆盖) ---------- +echo "[4/5] 写 protect.bash" + +cat > "$PROTECT_SCRIPT" < "\${DIR}/pt.log" 2>&1 +EOF + +chmod +x "$PROTECT_SCRIPT" + +# ---------- 启动新版本 ---------- +echo "[5/5] 启动新版本" + +LOG_FILE="${NEW_DIR}/pt.log" + +# 确保日志文件存在 +if [ ! -e "$LOG_FILE" ]; then + touch "$LOG_FILE" +fi + +# 设置日志权限 +chmod 777 "$LOG_FILE" + +# 后台启动新版本 +nohup "$PT_BIN" 0 >> "$LOG_FILE" 2>&1 & +disown + +echo "Upgrade finished, pt started in background" +exit 0 \ No newline at end of file diff --git a/config/x86_linux/config.json b/config/x86_linux/config.json new file mode 100644 index 0000000..3d564e3 --- /dev/null +++ b/config/x86_linux/config.json @@ -0,0 +1,174 @@ +{ + "version": "v1.0.0", + "dsp": { + "url": "0.0.0.0", + "port": 6000, + "center_freq": 800000, + "band_width": 6000, + "sample_rate": 7500, + "fft_point_number": 1024, + "fft_period_ms": 50, + "fft_win_type": "blkmc", + "library_path": "@/libdll_source.so", + "filename_prefix": "ECAP_", + "filename_suffix": ".dat", + "power_min": -200, + "power_max": 200, + "power_offset": -166, + "frame_rate": 30, + "debug": false + }, + "drogon": { + "app": { + "threads_num": 0 + } + }, + "mlat": { + "enable": false, + "merge": false, + "library_path": "@/mlat_source.dll", + "function_name": "read" + }, + "web_server": { + "webapp": "@/wwwroot", + "tiles": "@/tiles" + }, + "device": { + "net": [ + { + "key": "device", + "ip": "192.168.1.75", + "netmask": "255.255.255.0", + "gateway": "192.168.1.1", + "port": 9081 + }, + { + "key": "wifi", + "ip": "192.168.10.193", + "netmask": "255.255.255.0", + "gateway": "192.168.10.1", + "port": 80 + } + ] + }, + "mode_acs": { + "timeout_seconds": 60, + "read_milliseconds": 100, + "mode_s_max_num": 700000, + "mode_other_max_num": 10000, + "report_data_feed_msg_mum": 10000, + "monitor_msg_live": false, + "data_feed": { + "empty_wait_milliseconds": 10, + "packet_byte_size": 1, + "list": [ + { + "key": "Port_10003", + "enable": true, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true + }, + "port": 10003 + }, + { + "key": "Port_10004", + "enable": false, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "BIN", + "use_status": false, + "mode_s_output_type": "DF_11_17_18", + "use_mode_ac": false + }, + "url": "0.0.0.0", + "port": 10004 + }, + { + "key": "Port_10005", + "enable": false, + "type": "Data_Feed_TCP_Client", + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "NO_POS_Mode_S", + "use_mode_ac": false + }, + "url": "192.168.208.153", + "port": 10005 + }, + { + "key": "Port_30003", + "enable": false, + "type": "Data_Feed_TCP_Server", + "output_format": { + "type": "SBS", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true + }, + "port": 30003 + } + ] + }, + "data_source": { + "list": [ + { + "key": "dll", + "enable": true, + "base_station_show": true, + "aircraft_show": true, + "color": "#6c5552fa", + "aircraft_pixel_size": 34, + "lat": 37.510269, + "lon": 121.439950, + "alt": 1000.000000, + "update_form_gps": false, + "type": "Dll_Data_Source", + "library_path": "@/libdll_source.so", + "function_name": "adsb_read", + "data_type": "BIN_Blank" + }, + { + "key": "binary_data48__2025-01-06.222333", + "enable": false, + "base_station_show": true, + "aircraft_show": true, + "color": "#4e545c", + "aircraft_pixel_size": 38, + "lat": 37.510269, + "lon": 121.439950, + "alt": 1000.000000, + "update_form_gps": false, + "type": "File_Data_Source", + "file_path": "C:\\wyc\\CLionProjects\\plantSunCat\\mainProjects\\Radarcape\\data\\BIN\\binary_data48__2025-01-06.2.log", + "data_type": "BIN", + "play_mode": "analysis" + } + ] + }, + "source_feed_relation": { + "list": [ + { + "key": "key_10003", + "enable": true, + "type": "First_Source_To_All_Feed_Relation" + } + ] + } + }, + "log": { + "open_mode": "Delete", + "piece_num": 10, + "size_MB": 10 + }, + "console": { + "record_playback": false, + "mode_s_console": false, + "post_request": false, + "mlat_server": true + } +} diff --git a/config/x86_linux/data_progress_config.json b/config/x86_linux/data_progress_config.json new file mode 100644 index 0000000..53fb584 --- /dev/null +++ b/config/x86_linux/data_progress_config.json @@ -0,0 +1,9 @@ +{ + "library_path": "@/libdll_source.so", + "center_freq": 200000, + "band_width": 6000, + "sample_rate": 60000, + "fft_point_number": 2048, + "fft_period_ms": 50, + "fft_win_type": "blkmc" +} \ No newline at end of file diff --git a/config/前端任务 b/config/前端任务 new file mode 100644 index 0000000..59d2806 --- /dev/null +++ b/config/前端任务 @@ -0,0 +1,7 @@ +前端构思 + + +那些接口写整体加上 一个数据源参数 +数据源可以设置颜色 默认随机色 + + diff --git a/doc/10003 b/doc/10003 new file mode 100644 index 0000000..bfad639 --- /dev/null +++ b/doc/10003 @@ -0,0 +1,74 @@ +9.3.1 Packetizing & Line Encoding +HULC Protocol uses the byte 0x1A as start-of-packet (SOP) marker. In order to avoid misinterpretation +of normal data bytes with value 0x1A as SOP, each occurrence of data byte 0x1A is doubled in the +data stream during transmission ('escaping'). It is thus necessary during reception to detect every +0x1A not followed by another 0x1A as an SOP marker and to reduce every occurrence of a double +0x1A to a single 0x1A data byte ('un-escaping'). +Example of a 4-byte long packet including SOP marker and 0x1A data bytes: +Before Escaping: 1A 32 27 1A E8 57 F0 1A 6C +During Transmission: 1A 32 27 1A 1A E8 57 F0 1A 1A 6C +After Un-Escaping: 1A 32 27 1A E8 57 F0 1A 6C + + + +Type Payload Size Description +0x31 4 bytes Mode-A/C raw data +0x32 7 bytes Mode-S Short Squitter raw data +0x33 14 bytes Mode-S Extended Squitter raw data + + +ll Data messages (Type 0x31 & 0x32 & 0x33) have the following structure: +0x1A TYPE Timestamp RSSI Data (Mode-S or Mode-A/C) + + +Fields: +TYPE 8-bit 0x31 or 0x32 or 0x33 for Mode-A/C or Mode-S short or extended squitter respectively +Timestamp 48-bit1 + Upper 18 bits are seconds since last midnight 00:00:00 UTC + Lower 30 bits are nanoseconds of current second +RSSI 8-bit Logarithmic field-strength indicator, uncalibrated +Data N * 8-bit raw data, N=4 for Type 0x31, N=7 for TYPE 0x32 and N=14 for TYPE 0x33 +1) MSB-first + + +Timestamps: +There are two distinct timestamp formats, absolute and relative timestamps depending on +whether a GPS receiver is attached or not. To find out which format is used check bit 15 +(Gps Detected) in the status flag field of the HULC Status Message. Both timestamp formats +are compatible with the respective timestamp formats used in the Beast Binary Protocol. +Relative Timestamp (No GPS): +Without a GPS receiver attached the timestamp is a 48-bit unsigned number counting 12 +MHz cycles. The timestamp will wrap approximately every 271 days. +Absolute Timestamp (With GPS): +If a suitable GPS receiver is attached the 48-bit timestamp field is divided into the upper +18 bits which count the seconds since last midnight 00:00:00 UTC and the lower 30 bits +which carry the nanoseconds of the current second. Timestamp nanoseconds are +guaranteed to be less than one complete second. +Example: 0x3746554be940 = (56601 sec + 357296448 nsec) = 15:43:21.357296448 +UTC + + +1A 31 +3A 52 56 53 02 E1 +8F +37 5A 1A A3 + +1A 31 +3A 52 56 53 02 E1 +8F +37 5A 1A 1A A3 + +1A 31 +4B 03 5B 02 E1 90 +10 +1E 89 50 + +1a31 +19 ea b6 3a 1f 91 +65 +01 47 + +1a31 +19 b4 18 c3 59 0c +6d +32 10 \ No newline at end of file diff --git a/doc/10003_zh b/doc/10003_zh new file mode 100644 index 0000000..0112b22 --- /dev/null +++ b/doc/10003_zh @@ -0,0 +1,39 @@ +数据包封装与行编码 +HULC协议使用字节0x1A作为数据包的起始标记(SOP)。为了避免误将正常数据字节0x1A解释为SOP标记,每次遇到数据字节0x1A时,会在数据流中对其进行转义(即“转义”操作), +即将0x1A替换为两个0x1A字节。接收端需要在接收时识别每个后续不是0x1A的0x1A字节作为SOP标记,并将每次出现的连续0x1A字节还原为一个0x1A的数据字节(即“解除转义”操作)。 +以下是一个包含SOP标记和数据字节0x1A的数据包示例: +转义前: 1A 32 27 1A E8 57 F0 1A 6C +传输中: 1A 32 27 1A 1A E8 57 F0 1A 1A 6C +解除转义后: 1A 32 27 1A E8 57 F0 1A 6C + + + + +所有类型的数据消息(0x31、0x32、0x33)具有以下结构: +0x1A TYPE 时间戳 RSSI数据 Mode-S或Mode-A/C + 1 1 6 1 2/7/14 +各字段说明: + TYPE(类型):8位,表示数据类型。0x31代表Mode-A/C,0x32代表Mode-S短报文,0x33代表Mode-S扩展报文。 + 时间戳(Timestamp):48位,包含两个部分: + 高18位:自当天00:00:00 UTC以来的秒数。 + 低30位:当前秒的纳秒部分。 + RSSI(信号强度指示):8位,表示信号强度的对数值(未校准)。 + 数据(Data):N * 8位的原始数据,具体长度取决于数据类型: + 对于Type 0x31,数据长度为2字节; + 对于Type 0x32,数据长度为7字节; + 对于Type 0x33,数据长度为14字节。 + 备注:数据采用MSB(最高位优先)格式。 + + + +时间戳格式 +时间戳有两种格式,分别是绝对时间戳和相对时间戳,取决于是否连接了GPS接收器。可以通过检查HULC状态消息中的状态标志字段的第15位(Gps Detected)来确定使用的是哪种格式。两种时间戳格式与Beast二进制协议中的相应时间戳格式兼容。 +1.相对时间戳(无GPS): +如果没有连接GPS接收器,则时间戳是一个48位的无符号整数,表示12 MHz的时钟周期数。该时间戳大约每271天会回绕一次。 +2.绝对时间戳(有GPS): +如果连接了适当的GPS接收器,则48位的时间戳字段分为两部分: +高18位:表示自当天00:00:00 UTC以来的秒数。 +低30位:表示当前秒的纳秒部分。纳秒数保证小于1秒。 + +示例: 时间戳0x3746554be940代表的时间为: +56601秒 + 357296448纳秒 = UTC时间:15:43:21.357296448 \ No newline at end of file diff --git a/doc/11.txt b/doc/11.txt new file mode 100644 index 0000000..b9136e6 --- /dev/null +++ b/doc/11.txt @@ -0,0 +1,78 @@ +AIVDM/AIVDO Sentence Layer +AIVDM/AIVDO is a two-layer protocol. The outer layer is a variant of NMEA 0183, the ancient standard for data interchange in marine navigation systems; NMEA 0183 is described at [NMEA]. +Here is a typical AIVDM data packet: +!AIVDM,1,1,,B,177KQJ5000G?tO`K>RA1wUbN0TKH,0*5C +And here is what the fields mean: +Field 1, !AIVDM, identifies this as an AIVDM packet. +Field 2 (1 in this example) is the count of fragments in the currently accumulating message. The payload size of each sentence is limited by NMEA 0183’s 82-character maximum, so it is sometimes required to split a payload over several fragment sentences. +Field 3 (1 in this example) is the fragment number of this sentence. It will be one-based. A sentence with a fragment count of 1 and a fragment number of 1 is complete in itself. +Field 4 (empty in this example) is a sequential message ID for multi-sentence messages. +Field 5 (B in this example) is a radio channel code. AIS uses the high side of the duplex from two VHF radio channels: AIS Channel A is 161.975Mhz (87B); AIS Channel B is 162.025Mhz (88B). In the wild, channel codes '1' and '2' may also be encountered; the standards do not prescribe an interpretation of these but it’s obvious enough. +Field 6 (177KQJ5000G?tO`K>RA1wUbN0TKH in this example) is the data payload. We’ll describe how to decode this in later sections. +Field 7 (0) is the number of fill bits requires to pad the data payload to a 6 bit boundary, ranging from 0 to 5. Equivalently, subtracting 5 from this tells how many least significant bits of the last 6-bit nibble in the data payload should be ignored. Note that this pad byte has a tricky interaction with the [ITU1371]requirement for byte alignment in over-the-air AIS messages; see the detailed discussion of message lengths and alignment in a later section. +The *-separated suffix (\*5C) is the NMEA 0183 data-integrity checksum for the sentence, preceded by "*". It is computed on the entire sentence including the AIVDM tag but excluding the leading "!". This checksum is the XOR of all bytes in the payload. +For comparison, here is an example of a multi fragment sentence with a nonempty message ID field: +!AIVDM,2,1,3,B,55P5TL01VIaAL@7WKO@mBplU@qc:0001R:t?tUFfnT03wj4h4Id,0*73 +!AIVDM,1,1,,A,E:hTsH1Pb7W@LHHqIHKqIHIJqpqDEpF8:eLFp1088;AP02,4*0E +// + +!AIVDM,1,1,,B,177KQJ5000G?tO`K>RA1wUbN0TKH,0*5C +下面是字段的含义: + +字段 1,!AIVDM,标识这是一个 AIVDM 数据包。 +字段 2(此示例中为 1)是当前正在累积的消息中的片段总数。由于每条 NMEA 0183 语句的最大有效负载大小为 82 字符,因此有时需要将有效负载拆分为多个片段语句。 +字段 3(此示例中为 1)是当前语句的片段编号,采用基于 1 的计数。如果片段总数为 1 且片段编号为 1,则该语句本身是完整的。 +字段 4(此示例为空)是多片段消息的序列消息 ID。 +字段 5(此示例中为 B)是无线电频道代码。AIS 使用两个 VHF 无线电频道的高端:AIS A频道是 161.975 MHz(87B),AIS B频道是 162.025 MHz(88B)。在实际应用中,也可能遇到频道代码 '1' 和 '2';标准并没有规定这些代码的含义,但显然它们也有明显的区分。 +字段 6(此示例中的 177KQJ5000G?tOK>RA1wUbN0TKH`)是数据有效载荷。稍后我们将描述如何解码它。 +字段 7(此示例中的 0)是填充位的数量,用于将数据有效载荷填充到 6 位的边界,范围为 0 到 5。相应地,从这个数字减去 5 就是告诉我们在数据有效载荷的最后一个 6 位字节中需要忽略的最低有效位数。需要注意的是,这个填充字节与 [ITU1371] 对于无线电 AIS 消息的字节对齐要求有一个复杂的相互作用,稍后会详细讨论消息长度和对齐问题。 +以 * 分隔的后缀(*5C)是该语句的 NMEA 0183 数据完整性校验和,计算方式是对整个语句进行异或运算,包括 AIVDM 标签,但不包括前导的 ! 字符。 +为了便于对比,这里是一个多片段语句的示例,其中消息 ID 字段不为空: + + + +!AIVDM,2,1,3,B,55P5TL01VIaAL@7WKO@mBplU@ 使用绝对时间戳 +x1xx xxxx xxxx xxxx - GPS 有效 (GGA, RMC 数据存在) +xx1x xxxx xxxx xxxx - GPS 当前有有效定位 +xxx1 xxxx xxxx xxxx - 高精度绝对时间通过 PPS 可用 +xxxx xxxx 1xxx xxxx - 启动以来的 TX-队列溢出(如果使用硬件握手通常会发生) +xxxx xxxx x1xx xxxx - 上一秒发生的 TX-队列溢出(可能由于硬件握手) +xxxx xxxx xx1x xxxx - 发现过多的 NMEA 数据(除 GGA/RMC 外) \ No newline at end of file diff --git a/doc/icao解码.txt b/doc/icao解码.txt new file mode 100644 index 0000000..f353bd8 --- /dev/null +++ b/doc/icao解码.txt @@ -0,0 +1,50 @@ +https://mode-s.org/1090mhz/content/mode-s/1-basics.html + + + +p(x):crc计算结果 + +符号解释 +A:是ICAO地址 +P:是后24位置零,crc的计算结果,任何消息都能算这个值 +AP=A^P + +异或可逆性性 +AP = A^P +A = AP^P +P = AP^A +DP = BDS^AAA^P //DP是数据奇偶校验,目前没看到那种24位,存这种东西 + + +df = 11 17 18 : 消息后24位存放P, A明文存放在消息中,直接获取 +df = 0 4 5 16 20 21 消息后24位存放AP, 先计算P,然后根据 A=AP^P 计算出A + + +// 上面这个df pyModeS-master 项目 源码py_common.py :119 +// pyModeS-master项目 作者也是 那个文档的作者 + +def icao(msg: str) -> Optional[str]: +"""Calculate the ICAO address from an Mode-S message. + + Applicable only with DF4, DF5, DF20, DF21 messages. + + Args: + msg (String): 28 bytes hexadecimal message string + + Returns: + String: ICAO address in 6 bytes hexadecimal string + + """ + addr: Optional[str] + DF = df(msg) + + if DF in (11, 17, 18): + addr = msg[2:8] + elif DF in (0, 4, 5, 16, 20, 21): + c0 = crc(msg, encode=True) + c1 = int(msg[-6:], 16) + addr = "%06X" % (c0 ^ c1) + else: + addr = None + + return addr diff --git a/doc/radarcape_状态消息.txt b/doc/radarcape_状态消息.txt new file mode 100644 index 0000000..216e413 --- /dev/null +++ b/doc/radarcape_状态消息.txt @@ -0,0 +1,65 @@ +当选择 GPS 时间戳时,状态帧由 1PPS 脉冲触发,在传统 12MHz 时间戳的情况下随机触发。 +状态帧包含有效的时间戳 +信号电平值为零 + + + +1. 消息内容 状态帧 +0x34 包含以下信息: + Byte 0:FPGA 开关设置状态 + Byte 1:1PPS 事件时的纳秒级时间偏移量 + Byte 2:GPS 相关状态信息 + + +2. Byte 0: +Byte 0 反映了 FPGA 的开关设置,FPGA 的这些设置会直接影响 原始数据端口 (10002-10006),以及高层功能的数据可用性。 +FPGA 开关设置: + Bit 位 '1'(启用) '0'(禁用) + Bit 0 数据输出模式 (端口 10002 适用):二进制格式 AVR Hexdump 格式 + Bit 1 原始数据帧类型:仅 DF11、DF17 和 DF18 无预过滤,输出所有帧类型 + Bit 2 AVR 格式中的时间戳:启用 禁用 + Bit 3 未使用 未使用 + Bit 4 时间戳模式:GPS 传统 12MHz + Bit 5 串行流控:禁用 启用 + Bit 6 1-bit 前向纠错 (FEC):禁用 启用 + Bit 7 Mode-A/C 解码:启用 禁用 + +3. Byte 1:1PPS 事件的纳秒偏移 + 当处于 GPS 时间戳模式 时: + Byte 1 记录 1PPS 事件时,纳秒定时器的偏移量。 + 该值的单位为 15.625ns,正常范围为 -3 至 +3。 + + +4. Byte 2:GPS 状态信息 + Byte 2 记录 GPS 相关的状态,具体比特位定义如下: + Bit 位 '1'(正常) '0'(异常) + Bit 0 天线状态:良好 失败 + Bit 1 至少跟踪 1 颗卫星:是 否 + Bit 2 至少跟踪 3 颗卫星:是 否 + Bit 3 时间基准:UTC 时间 GPS 时间(相差 8 秒) + Bit 4 未使用 未使用 + Bit 5 未使用 未使用 + Bit 6 未使用 未使用 + Bit 7 Daysec(一天的秒数范围):0 - 86399 1 - 86400 + + +5. 相关说明 +GPS 时间与 UTC 时间 + GPS 时间比 UTC 时间 早 8 秒。 + Bit 3 可以用于区分系统使用的时间基准。 + +FPGA 开关设置对数据的影响 + Bit 0 影响 数据端口 10002 的输出格式。 + Bit 1 影响 是否预过滤帧类型。 + Bit 4 选择 时间戳模式,影响所有时间同步相关功能。 + +总结 + 状态帧 0x34 提供了 FPGA 设置、1PPS 偏移量 和 GPS 状态,对于时间同步、数据格式选择和 GPS 信号监测至关重要。 + + +官网相关资料 +https://wiki.jetvision.de/wiki/Mode-S_Beast:Data_Output_Formats +https://wiki.jetvision.de/wiki/Radarcape:Version_History#Status_Frame_0x34_contents_were_extended_with_GPS_and_UTC_information + + +实际采集信息 和官网文档对不上 官网好多东西没说 \ No newline at end of file diff --git a/doc/模块数据格式分析 b/doc/模块数据格式分析 new file mode 100644 index 0000000..0fc04f7 --- /dev/null +++ b/doc/模块数据格式分析 @@ -0,0 +1,53 @@ + +@000217047C020143; +@0002170A69FE5D781F835AFF22; +@0002170759188D781F832320C1B8E31C200772A4; + + + +@000217047C02 0143; + +当启用MLAT格式时有下面三种格式 +@ + 6字节(48位)MLAT时间戳,1 字节信号电平,2 字节 Mode-AC + ; +@ + 6字节(48位)MLAT时间戳,1 字节信号电平,7 字节 Mode-S 短帧 + ; +@ + 6字节(48位)MLAT时间戳,1 字节信号电平,14 字节 Mode-S 长帧 + ; + +1字节信号电平 +功率大小 以 dBm 为单位 +时间戳格式 (18位 当天的秒数 + 30位 当天的纳秒数) +例: 时间戳 244bbb9ac9f0 +高18位 秒数 = 37166 +低30位 纳秒 = 999999984 +例如: +@016CE3671C74 7700; +@016CE3671C74 23FFE7AB7BFCAB; +@016CE3671AA8 A800199A8BB80030A8000628F400; + +参考链接 +https://wiki.jetvision.de/wiki/Mode-S_Beast:Data_Output_Formats +https://wiki.jetvision.de/wiki/Radarcape:Version_History#The_GPS_timestamp +https://wiki.jetvision.de/wiki/Radarcape:Version_History#Signal_level_in_dBm + + +1110 0000 0011 0000 + +2) https://en.wikipedia.org/wiki/Binary_scaling#Binary_angles + Status Flags: + 1xxx xxxx xxxx xxxx - GPS device detected -> Using absolute timestamp + x1xx xxxx xxxx xxxx - GPS valid (GGA, RMC present) + xx1x xxxx xxxx xxxx - GPS currently has a valid fix + xxx1 xxxx xxxx xxxx - High accuracy absolute time is available via PPS + xxxx xxxx 1xxx xxxx - TX-Queue overflow since start-up (normal if HW-handshaking is used) + xxxx xxxx x1xx xxxx - TX-Queue overflow during last second (probably due to HW-handshaking) + xxxx xxxx xx1x xxxx - Excessive NMEA found (other than GGA/RMC) + + +1a48 +0118 + +9607f81e +e030 + +000000000000000000000000000000000000 + +e030 diff --git a/doc/泰山派开发记录.md b/doc/泰山派开发记录.md new file mode 100644 index 0000000..8837ecc --- /dev/null +++ b/doc/泰山派开发记录.md @@ -0,0 +1,418 @@ +# ==应用开发== + +## APP + +CMakeLists.txt + +```cmake +cmake_minimum_required(VERSION 3.10) + +########################## 可单独生成 toolchain.cmake ################################################# +set(CMAKE_SYSTEM_NAME Linux) # 设置目标系统名字 +set(CMAKE_SYSTEM_PROCESSOR arm) # 设置目标处理器架构 + +# 该变量的值会传递给 gcc 编译器的--sysroot 选项,也就是--sysroot=${CMAKE_SYSROOT},--sysroot 选项指定了编译器的 sysroot 目录,也就是编译器的系统根目录,编译过程中需要链接的库、头文件等,就会去该目录下寻找,譬如标准 C 库、标准 C 头文件这些。 +set(CMAKE_SYSROOT /home/ml/sdk/buildroot/output/rockchip_rk3566/host/aarch64-buildroot-linux-gnu/sysroot/) # ⭐指定了目标系统的根目录 + +# 设置编译器路径 +set(CMAKE_C_COMPILER /home/ml/sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER /home/ml/sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-g++) +# 为编译器添加编译选项 +#set(CMAKE_C_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7") +#set(CMAKE_CXX_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7") + +# NEVER:仅搜索主机系统路径。 +# ONLY:只搜索 CMAKE_SYSROOT。 +# BOTH:将搜索主机系统路径和 CMAKE_SYSROOT 中的路径。 +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # 限制find_library()的搜索路径。 +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # 限制find_file()和 find_path()的搜索路径。 + +#set(ENV{LD_LIBRARY_PATH} "${CMAKE_SYSROOT}/lib:$ENV{LD_LIBRARY_PATH}") # 添加库路径到 LD_LIBRARY_PATH + +#set(CMAKE_LIBRARY_PATH "/opt/atk-dlrk356x-toolchain/lib/") # 添加依赖库find路径:用于指定库文件被find_library()时的目录列表。 +#set(CMAKE_INCLUDE_PATH "/opt/atk-dlrk356x-toolchain/include") # 添加依赖库头文件find路径:用于指定find_path()头文件时应考虑的额外目录列表。 + +set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/myinstall) # 设置安装目录,当前CMakeLists.txt所在目录下的myinstall文件夹 +###################################################################################################### + +# 设置项目名称 +project(mongoose VERSION 1.0) + +# 包含工具链文件 +set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/toolchain.cmake CACHE STRING "Toolchain file") + +# 查找所有源文件 +file(GLOB SOURCES "${CMAKE_SOURCE_DIR}/*.cpp" "${CMAKE_SOURCE_DIR}/*.c") + +# 添加可执行文件 +add_executable(${PROJECT_NAME} ${SOURCES}) + +# 指定头文件目录 +target_include_directories(${PROJECT_NAME} PRIVATE mongoose) +``` + + + +```sh +root@RK356X:/mnt# ./mongoose +terminate called after throwing an instance of 'std::runtime_error' + what(): locale::facet::_S_create_c_locale name not valid +Aborted +``` + +## WIFI&蓝牙 + +泰山派开发板板载了WIFI+蓝牙二合一模块,这里默认贴的是AP6212。 + +```sh +vi /data/cfg/wpa_supplicant.conf # 修改配置文件 +wpa_supplicant -B -i wlan0 -c /data/cfg/wpa_supplicant.conf # wpa_supplicant重新读取配置文件,启动,后面wpa_cli会和它进行通讯。 +wpa_cli -i wlan0 -p /var/run/wpa_supplicant scan # [可选]WIFI扫描,wpa_cli跟wpa_supplicant通信,确保wpa_supplicant运行。 +wpa_cli -i wlan0 -p /var/run/wpa_supplicant scan_results # [可选]显示扫描结果 +wpa_cli -i wlan0 -p /var/run/wpa_supplicant reconnect # 发起连接 +ping lckfb.com +``` + + + +## LED控制 + +RK3588的GPIO引脚号的计算方式,参考如下公式:( A、B、C、D 分别代表数值 0、1、2、3) + +GPIO3_A1 = 3x32 + 0x8 +1 = 97 + +| | 复用功能 | GPIO | 引脚编号 | 复用功能 | 复用功能 | | | +| -------- | ------------ | -------- | -------- | -------- | -------- | ----------- | ---- | +| | 3.3V | 3.3V | 1 | 2 | 5V | 5V | | +| | I2C2_SDA_M0 | GPIO0_B6 | 3 | 4 | 5V | 5V | | +| | I2C2_SCL_M0 | GPIO0_B5 | 5 | 6 | GND | GND | | +| | GPIO | GPIO1_A4 | 7 | 8 | GPIO3_B7 | UART3_TX_M1 | | +| | GND | GND | 9 | 10 | GPIO3_C0 | UART3_RX_M1 | | +| 97 | GPIO | GPIO3_A1 | 11 | 12 | GPIO3_C4 | PWM14_M0 | | +| 98 | GPIO | GPIO3_A2 | 13 | 14 | GND | GND | | +| 99 | GPIO | GPIO3_A3 | 15 | 16 | GPIO3_A4 | GPIO | 100 | +| | 3.3V | 3.3V | 17 | 18 | GPIO3_A5 | GPIO | 101 | +| | SPI3_MOSI_M1 | GPIO4_C3 | 19 | 20 | GND | GND | | +| | SPI3_MISO_M1 | GPIO4_C5 | 21 | 22 | GPIO3_A6 | GPIO | 102 | +| | SPI3_CLK_M1 | GPIO4_C2 | 23 | 24 | GPIO4_C6 | SPI3_CS0_M1 | | +| | GND | GND | 25 | 26 | GPIO3_A7 | | | +| | I2C3_SDA_M1 | GPIO3_B6 | 27 | 28 | GPIO3_B5 | I2C3_SCL_M1 | | +| | GPIO | GPIO3_B0 | 29 | 30 | GND | GND | | +| | GPIO | GPIO3_C2 | 31 | 32 | GPIO3_C5 | PWM15_IR_M0 | | +| 3 RK_PB1 | PWM8_M0 | GPIO3_B1 | 33 | 34 | GND | GND | | +| 3 RK_PB2 | PWM8_M0 | GPIO3_B2 | 35 | 36 | GPIO3_C3 | GPIO | | +| | GPIO | GPIO0_B7 | 37 | 38 | GPIO3_B3 | GPIO | | +| | GND | GND | 39 | 40 | GPIO3_B4 | GPIO | | + +```sh + +root@RK356X:/sys/class/gpio/gpio25# echo 97 > /sys/class/gpio/export +root@RK356X:/sys/class/gpio/gpio25# echo 98 > /sys/class/gpio/export +root@RK356X:/sys/class/gpio/gpio25# echo 99 > /sys/class/gpio/export +root@RK356X:/sys/class/gpio/gpio25# echo 100 > /sys/class/gpio/export +root@RK356X:/sys/class/gpio/gpio25# echo 101 > /sys/class/gpio/export +root@RK356X:/sys/class/gpio/gpio25# echo 102 > /sys/class/gpio/export + +echo out > /sys/class/gpio/gpio97/direction +echo out > /sys/class/gpio/gpio98/direction +echo out > /sys/class/gpio/gpio99/direction +echo out > /sys/class/gpio/gpio100/direction +echo out > /sys/class/gpio/gpio101/direction +echo out > /sys/class/gpio/gpio102/direction + +echo 1 > /sys/class/gpio/gpio97/value +echo 1 > /sys/class/gpio/gpio98/value +echo 1 > /sys/class/gpio/gpio99/value +echo 1 > /sys/class/gpio/gpio100/value +echo 1 > /sys/class/gpio/gpio101/value +echo 1 > /sys/class/gpio/gpio102/value //待验证?? +``` + + + +## ADS-B模块:GNS5894 + +**模块测试:** + +```sh +stty -F /dev/ttyS4 3000000 +cat /dev/ttyS4 +``` + +**控制命令:协议切换** + +11引脚:UART PS(低:HULC协议;高:Text协议),HULC协议的波特率921600、8N1、**RTS/CTS**,Text协议的波特率3000000、8N1、**RTS/CTS**。 + +Text协议在很大程度上与所谓的 AVR 协议兼容,而 HULC 协议的 Mode-S 消息与所谓的 Beast 二进制协议兼容,方便将 GNS 5894 TAC 与现有软件连接。 + +### 1.Text协议 + +**命令格式** + +每条消息都以 '#' (0x23) 开头,以 '' (0x0D, 0x0A) 结尾。中间是 1 到 16 个字节,采用 2 位十六进制表示,使用大写字母,用 '-' (0x2D) 分隔。 + +```sh +# command-message举例: +#00 # ASCII码发送,需要结尾发送回车换行 +23 30 30 0D 0A # HEX发送,0D 0A就是回车换行 +#43-12-00-00-00-00-00-00-00-00-00-00-00-00-00-00 # ASCII码接收 +23 34 33 2D 31 32 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 2D 30 30 0D 0A # HEX接收 + +#43-02 # ASCII码发送 +23 34 33 2D 30 32 0D 0A # HEX发送 +``` + +**模块命令** + +1. 查看版本 + +```sh +#00 # 版本请求 +#00-00-80-04-81-yy-ww-bb-xx-xx-xx-xx-xx-xx-xx-xx +#00 +#00-00-80-04-81-15-0A-11-01-02-03-01-00-00-00-00 +``` + +2. 模式读取/设置 + +| 低四位 | 输出模式 | 高四位 | 模式标志 | +| ------ | --------------------------------- | ------ | ------------------------------- | +| 0x00 | Mode-S 数据关闭 | 0x10 | MLAT 模式(不与 0x80 结合使用) | +| 0x01 | 保留 | 0x80 | RSSI 模式(不与 0x10 结合使用) | +| 0x02 | 默认输出所有Mode-S & Mode-A/C数据 | | | +| 0x03 | 仅输出 ADS-B 数据 (DF17-19) | | | +| 0x04 | 仅输出具有有效 CRC 的 ADS-B 数据 | | | + +注:MLAT 模式就是**时间戳**加数据;RSSI 模式就是**信号强度**加数据。但是不能同时输出这两种模式。 + +```sh +# 设置模式 +# 通过 MLAT 模式(0x10)输出所有Mode-S & Mode-A/C数据(0x02) +#43-12 +#43-12-00-00-00-00-00-00-00-00-00-00-00-00-00-00 + +# 读取当前模式 +#43 # 模式命令/请求 +#43-02-00-00-00-00-00-00-00-00-00-00-00-00-00-00 # 返回模式02 +``` + +3. Mode-S & Mode-A/C 数据帧接收 + +在 0x02模式(接收所有 DF 数据)下,12位或56位或112位原始数据帧被输出为4字节或7字节或14字节ASCII帧,MSB优先。以 '*' 开头,以 ';' 结束。 + +![image-20240914144813244](泰山派开发记录/image-20240914144813244.png) + +| | | | | | | +| ---- | ---- | ---- | ---- | ---- | ---- | +| | | | | | | + + + +```sh +# 正常模式(默认): +*<7或14字节数据> ; # Mode-S 格式 +*5D4951B4192B49; # DF11 Mode-S 7 字节(短报文 Short Squitter) +*5D780AE871C60D; # DF11 All-call reply +*024101315B7DB6; # Short air-air surveillance (ACAS) +*20000210BE8DCE; # Long air-air surveillance (ACAS) + +*8D4951B49945B40CE8589F6181B6; # DF18 Mode-S 14 字节(扩展报文 Ext.Squitter) +*8D780AE899094D8CF0A02FA77CD2; # Extended squitter + +*<2字节2的2位八进制数据>; # Mode-A/C 格式 +*5734; # Mode-A/C 数据 + +# MLAT 模式(可选): +@<6字节时间戳><4/7/14字节的2位数据>;<6字节时间戳>:6字节MSB优先自由运行12MHz计数器值,十六进制为12位。 +@112233445566 5D4951B4192B49; # Short Squitter +@1122334455668D4951B49945B40CE8589F6181B6; # Ext. Squitter +@1122334455665734; # Mode-A/C + +# RSSI 模式(可选): ++<1字节RSSI> <4/7/14字节的2位数据>>; +<1字节RSSI>: 1字节未校准的对数RSSI值,以2位十六进制表示 ++AB5D4951B4192B49; ++AB8D4951B49945B40CE8589F6181B6; ++AB5734; +``` + +在 0x02+ RSSI 模式(所有模式-S & 模式-A/C数据的接收器,带有附加信号强度指示器)下,12 位或 56 位或 112 位原始数据帧输出为 3 字节、8 字节或 15 字节 ASCII 帧,MSB 优先。每个 ASCII 帧都以 '+' 开头,以 ';' 结束。 + +“+”后面的第一个字节是当前 ADS-B 帧的信号强度指示器。 + +重要提示: + +在数据之前的信号强度指示器(Signal Strength Indicator)仅仅是一个接收到的帧的场强(即信号强度)的指示器,而不是一个可以精确计算出与发射器之间距离的测量值。换句话说,这个信号强度指示器只告诉你信号的强弱,但它不能直接、确定地用来计算出你离信号发射器的确切距离。 + +该指示器所表示的值大约范围在0x20到0xd5之间。其中,0x20代表较远的距离(即信号较弱),而0xd5代表较近的距离(即信号较强)。这里的0x前缀表示这是一个十六进制数。在实际应用中,不同的设备或系统可能会对这个范围有不同的解释或处理方式,但基本原理相同:它提供的是一个信号强度的相对度量,而不是与发射器之间距离的直接度量。 + +### 2.HULC协议 + +参考`GNS5894TAC_ADSBModul_datasheet_V1.1.pdf`,待整理 + + + +## AIS模块:GNS5851 + +**模块测试:** + +```sh +stty -F /dev/ttyS3 115200 +cat /dev/ttyS3 +``` + + + + + + +# 术语 + +## Mode-S 和 Mode-A/C + +Mode-S (Mode Select) 和 Mode-A/C (Mode A and Mode C) 是航空监视系统中使用的不同类型的应答信号格式,它们分别用于不同的目的和提供不同的信息。 + +**Mode-A/C 帧** + +Mode-A/C 代表的是传统的二次监视雷达 (SSR, Secondary Surveillance Radar) 所使用的信号格式。Mode-A 和 Mode-C 是两个不同的信号模式,它们通常结合使用来提供飞机的身份识别和高度信息。 + +- **Mode-A (Mode Alpha)**:提供身份识别信息。飞机上的应答器会在收到询问信号后发送一个四位数的代码,这个代码由空中交通管制员分配给飞机,用于区分不同的飞行器。 +- **Mode-C (Mode Charlie)**:提供高度信息。与 Mode-A 类似,但 Mode-C 传输的是飞机的高度信息,通常以百英尺为单位。 + +**Mode-S 帧** + +Mode-S (Mode Select) 是一种更为现代的应答信号格式,它提供了比 Mode-A/C 更多的功能和更高的数据传输能力。Mode-S 被设计用于增强安全性和效率,尤其是在繁忙的空域中。 + +- **Mode-S 基本模式**:类似于 Mode-A/C,但也提供了飞机的身份识别信息。 +- **Mode-S 扩展消息 (ES)**:这是 Mode-S 的扩展版本,可以携带更多的信息,如飞机的位置、速度和其他相关数据。Mode-S ES 可以支持 ADS-B (Automatic Dependent Surveillance-Broadcast) 技术,这是一种自动广播监视技术,允许飞机自主广播其位置和其他飞行信息。 +- **Mode-S 增强型**:除了基本的身份识别和 ADS-B 信息外,还可以支持更多功能,如选择性呼叫、数据链通信等。 + +**区别** + +- **信息量**:Mode-S 能够携带更多的信息,而不仅仅是简单的识别和高度信息。 +- **安全性**:Mode-S 支持加密通信,增加了安全性。 +- **数据速率**:Mode-S 的数据速率更高,可以更快地传输更多的信息。 +- **兼容性**:Mode-A/C 是传统的格式,大多数旧的飞机仍然使用;而 Mode-S 是更新的技术标准,主要用于现代化的飞机和先进的空中交通管理系统。 + +总的来说,Mode-S 提供了更多的功能,增强了空中交通管理的能力,特别是在繁忙的空域中提高了安全性和效率。然而,由于 Mode-A/C 仍在广泛使用,因此许多雷达系统需要同时支持这两种模式。 + +## MLAT服务器 + +MLAT服务器,全称Multiple Lateral-sounding Radar(多点定位雷达)服务器,是一种利用多个地面站接收来自飞行器的广播式自动相关监视(ADS-B)信号,通过测量信号到达不同地面站的时间差,并结合地面站的精确位置信息,来计算飞行器位置的技术平台。以下是关于MLAT服务器的详细解释: + +一、技术原理 + +- **信号接收**:MLAT系统通过多个地面站接收飞行器发射的ADS-B信号。 +- **时间差测量**:由于信号传播速度有限,不同地面站接收到信号的时间会有所不同。系统通过测量这些时间差,构建出一组方程。 +- **位置计算**:结合地面站的精确位置信息,利用三角定位的原理,通过解这组方程来求解飞行器的位置。 + +二、系统特点 + +- **无需额外设备**:MLAT系统不需要在飞行器上安装额外的设备,仅利用现有的ADS-B设备即可实现精确的定位。 +- **广泛适用性**:由于依赖的是ADS-B信号,MLAT系统可以应用于大多数已加装ADS-B应答机的飞行器。 +- **高精度**:通过多个地面站的联合观测,MLAT系统能够提供高精度的飞行器位置信息。 + +三、应用领域 + +- **航空交通管制**:MLAT技术为航空交通管制提供了实时、精确的飞行器位置信息,有助于提升管制效率和安全性。 +- **飞行器追踪**:在飞行器追踪领域,MLAT系统能够实现对飞行器的持续、精确追踪,为飞行器的运行管理提供有力支持。 +- **空中交通管理**:MLAT技术的应用有助于优化空中交通流量,减少航班延误和冲突,提升整体航空运输效率。 + +四、系统构建 + +- **硬件要求**:MLAT系统需要安装多个地面接收机(如RadarCape),并在一台服务器上安装多点定位中央处理系统软件。 +- **网络连接**:通过以太网、移动网络或互联网将地面接收机与中央MLAT服务器连接,实现数据的实时传输和处理。 +- **数据处理**:中央MLAT服务器接收来自地面接收机的数据,进行位置解算,并将结果提供给用户或相关系统。 + +综上所述,MLAT服务器是一种基于多点定位技术的服务器平台,通过接收和处理来自多个地面站的ADS-B信号,实现对飞行器的精确定位。该技术在航空交通管制、飞行器追踪和空中交通管理等领域具有广泛的应用前景和重要的实用价值。 + +## GNSS数据格式RMC/GGA + +RMC(推荐最小数据)和GGA(全球定位信息)数据句(sentences) + +1. RMC(Recommended Minimum Specific GNSS Data) + - RMC是一种GNSS(全球导航卫星系统)数据格式,它提供了卫星导航系统的最小推荐数据集。 + - RMC数据句包含了时间、位置、速度等基本信息,但不包括所有可能的GNSS数据字段。 + - 它被设计为在数据传输带宽有限的情况下,仍能提供足够的导航信息。 +2. GGA(Global Positioning System Fix Data) + - GGA是GPS(全球定位系统)数据格式的一种,用于提供关于GPS定位的信息。 + - GGA数据句包含了定位的时间、质量指示、纬度、经度、高度等关键信息。 + - 它通常用于GPS接收机的标准输出,以提供关于当前位置的详细信息。 + +## Mode-S协议 + +AA:地址公布 ; + +AC:海拔代码 ; + +AF:应用领域 ; + +AP:地址/奇偶校验 ; + +AQ:收购 ; + +CA:能力 ; + +CC:交联能力 ; + +CF:控制场 ; + +CL:代码标签 ; + +DF:下行格式 ; + +DI:标号标识 ; + +DR:下行请求 ; + +DS:数据选择器 ; + +ELM:Extended length message 扩长消息 ; + +FS:航班状态 ; + +IC:询问器代码 ; + +ID:Identità ;KE:控制,ELM ; + +MA:消息,Comm-A ; + +MB:消息,Comm-B ; + +MC:消息,Comm-C ; + +MD:消息,Comm-D ; + +ME:消息,扩展 squitter ; + +MU:消息,ACAS ; + +MV:消息,ACAS ; + +NC:C 段数 ; + +ND:D 段数 ; + +PC:协议 ; + +PI:奇偶校验/询问器标识符 ; + +PR:回复概率 ; + +RC:回复控制 ; + +RI:回复信息 ; + +RL:回复长度 ; + +RR:回复请求 ; + +SD:特殊标号; + +UF:上行格式 ; + +UM:实用程序消息 ; + +VS:垂直状态 + diff --git a/doc/网页文档.md b/doc/网页文档.md new file mode 100644 index 0000000..8f9c513 --- /dev/null +++ b/doc/网页文档.md @@ -0,0 +1,36 @@ + +GNS5894TAC_ADSBModul_datasheet 模块在线文档 +https://www.gns-electronics.de/wp-content/uploads/2021/02/GNS5894TAC_ADSBModul_datasheet_V1.3.pdf + + + + +AIS DAC +https://en.wikipedia.org/w/index.php?title=Maritime_identification_digits + +解码AIS在线网站 +https://www.aggsoft.com/ais-decoder.htm +批量解码AIS在线网站 +https://www.maritec.co.za/aisvdmvdodecoding + + + + +GPSD 开源代码 ais C语言开源代码 +https://gitlab.com/gpsd/gpsd +https://gpsd.gitlab.io/gpsd/AIVDM.html + + + +jetvision +https://wiki.jetvision.de/wiki/Mode-S_Beast:Data_Output_Formats +https://wiki.jetvision.de/wiki/Radarcape:Version_History#The_GPS_timestamp +https://wiki.jetvision.de/wiki/Radarcape:Version_History#Signal_level_in_dBm +https://wiki.jetvision.de/wiki/Radarcape:Version_History#Status_Frame_0x34_contents_were_extended_with_GPS_and_UTC_information + +mode-s解码网站 +// crc算法 +https://mode-s.org/1090mhz/content/ads-b/8-error-control.html +// mode-s基础知识 +https://mode-s.org/1090mhz/content/mode-s/1-basics.html + diff --git a/logrotate b/logrotate new file mode 100644 index 0000000..1593436 --- /dev/null +++ b/logrotate @@ -0,0 +1,3 @@ +https://zhuanlan.zhihu.com/p/1899057198333670933 + + diff --git a/main.cmake b/main.cmake new file mode 100644 index 0000000..ba36148 --- /dev/null +++ b/main.cmake @@ -0,0 +1,153 @@ +get_filename_component(name ${CMAKE_CURRENT_LIST_DIR} NAME) + +set(rely libarchive SQLiteCpp) +rcl_load_dependency_environment(drogon ${rely}) + + +message(STATUS "before find SQLiteCpp CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") + + + +message(STATUS "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") +message(STATUS "CMAKE_FIND_USE_CMAKE_PATH=${CMAKE_FIND_USE_CMAKE_PATH}") +message(STATUS "CMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH}") +message(STATUS "CMAKE_FIND_ROOT_PATH_MODE_PACKAGE=${CMAKE_FIND_ROOT_PATH_MODE_PACKAGE}") + + +find_package(Jsoncpp CONFIG) +find_package(Drogon) + + +set(Core Core_Static) + +if(1) + + + set(md "${CMAKE_CURRENT_LIST_DIR}/module") + + + if(WIN32) + set(cfg_dir ${CMAKE_CURRENT_LIST_DIR}/config) + + else() + set(cfg_dir ${CMAKE_CURRENT_LIST_DIR}/config/x86_linux) + endif() + + + + _create("dll" dll_source) + _create("exe" data_process) + _attach_include_dir(dll_source "${md}") + _attach_source_dir(dll_source "${md}/dll_source") + _attach_include_dir(data_process "${md}") + _attach_source_dir(data_process "${md}/dll_source") + + + + _depend(dll_source ${Core}) + _depend(data_process ${Core}) + _attach_post_build_file(dll_source ${cfg_dir}/data_progress_config.json + "${CMAKE_CURRENT_LIST_DIR}/data/BIN_Blank_Text/ADS-B_195_0205.txt") + + + _create("dll" mlat_source) + _attach_include_dir(mlat_source "${md}") + _attach_source_dir(mlat_source "${md}/mlat_source") + _depend(mlat_source ${Core}) + + _create("exe" mlat_source_exe) + _attach_include_dir(mlat_source_exe "${md}") + _attach_source_dir(mlat_source_exe "${md}/mlat_source") + _depend(mlat_source_exe ${Core}) + + + + + find_package(SQLiteCpp CONFIG REQUIRED) + + + + _create("exe" ecap_server) + _depend(ecap_server SSR) + _attach_include_dir(ecap_server "${md}") + _attach_source_dir(ecap_server "${md}/Local_Server") + target_link_libraries(ecap_server PRIVATE Drogon::Drogon) + target_link_libraries(ecap_server PRIVATE SQLiteCpp) + target_link_libraries(ecap_server PRIVATE ${ZLIB_LIBRARY}) + target_include_directories(ecap_server PRIVATE ${ZLIB_INCLUDE_DIR}) + + message("LibArchive_SUB_LIBRARIES ${LibArchive_SUB_LIBRARIES}") + target_link_libraries(ecap_server PRIVATE + LibArchive::LibArchive + ${LibArchive_SUB_LIBRARIES} + + ) + target_compile_definitions(ecap_server PRIVATE LIBARCHIVE_STATIC) + + + _attach_source_file(ecap_server "${md}/Local_Server_main.cpp") + _attach_post_build_file(ecap_server ${cfg_dir}/config.json) + + if(UNIX AND NOT APPLE) + # 添加进程间通信系统库 + target_compile_options(ecap_server PRIVATE -g -gstrict-dwarf -gdwarf-4 -fvar-tracking-assignments) + set(CMAKE_FOLDER "lzy") + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/lzy ${CMAKE_BINARY_DIR}/lzy) + unset(CMAKE_FOLDER) + set(ver v1.0.4) + set(WEB_DIR "${CMAKE_CURRENT_LIST_DIR}/webapp") + set(publish_root_dir "${CMAKE_CURRENT_LIST_DIR}/ver") + set(publish_dir "${publish_root_dir}/${ver}") + set(WEB_OUT_DIR "${publish_dir}/wwwroot") + set(PACKAGE_ZIP "${publish_dir}.zip") + set(NODE_DIR "C:/Users/wyc/AppData/Roaming/JetBrains/CLion2026.1/node/versions/24.15.0") + set(NPM_EXECUTABLE "${NODE_DIR}/npm.cmd") + if(NOT EXISTS "${NODE_DIR}/node.exe") + message(FATAL_ERROR "node.exe not found: ${NODE_DIR}/node.exe") + endif() + add_custom_target(ecap_server_package + COMMAND ${CMAKE_COMMAND} -E make_directory "${publish_dir}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${publish_dir} + COMMAND ${CMAKE_COMMAND} -E remove_directory "${WEB_OUT_DIR}" + COMMAND ${CMAKE_COMMAND} -E env + "PATH=${NODE_DIR};$ENV{PATH}" + ${CMAKE_COMMAND} -E chdir "${WEB_DIR}" + "${NPM_EXECUTABLE}" run build -- --outDir "${WEB_OUT_DIR}" --emptyOutDir + COMMAND ${CMAKE_COMMAND} -E remove -f "${PACKAGE_ZIP}" + COMMAND ${CMAKE_COMMAND} -E chdir "${publish_dir}" + ${CMAKE_COMMAND} -E tar cf "${PACKAGE_ZIP}" --format=zip -- . + COMMENT "Build frontend" + USES_TERMINAL + + VERBATIM + ) + add_dependencies(ecap_server_package ecap_server) + endif() + if(MSVC) + target_compile_definitions(ecap_server PRIVATE + _CRTDBG_MAP_ALLOC + ) + endif() + add_dependencies(ecap_server dll_source) +if(WIN32) + +endif() + _create("exe" test_drogon) + _attach_include_dir(test_drogon "${md}") + _attach_source_dir(test_drogon "${md}/test_drogon") + target_link_libraries(test_drogon PRIVATE Drogon::Drogon) +endif() + +rcl_unload_dependency_environment(drogon ${rely}) + + +include("${CMAKE_SOURCE_DIR}/0_cmake_library/ninja_build_analyze/export.cmake") + +register_ninja_build_analyze_target( + ninja_build_analyze_ctx # 分析上下文 target 名,自己起名 + "Core_Static" # 要分析的构建目标;空字符串表示 all + "${CMAKE_BINARY_DIR}/analyze" # 分析输出目录;空字符串表示 build/build_analysis + "ON" # 是否先 clean:ON/OFF + "30" # top 数量;空字符串表示 30 +) + diff --git a/module/Local_Server.rar b/module/Local_Server.rar new file mode 100644 index 0000000000000000000000000000000000000000..d913b6bc64505a1915fe911713badf0cd9980298 GIT binary patch literal 96523 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 +#include +#include +#include + +#include "SSR/Aircraft_Info.h" + +class Aircraft : public SSR::Aircraft_Info { +public: + explicit Aircraft(const std::string& icao); + + // 静态信息按 ICAO24 加载一次;航线信息仅在 callsign 变化时重新加载。 + // map 的键是外部数据源名称。 + std::map> external_database_info; + std::map> callsign_external_database_info; + bool external_database_info_loaded{}; + std::optional last_external_database_callsign; + + [[nodiscard]] std::shared_ptr external_database_row( + const std::string& resource_name) const { + const auto callsign_iter = callsign_external_database_info.find(resource_name); + if (callsign_iter != callsign_external_database_info.end()) { + return callsign_iter->second; + } + const auto iter = external_database_info.find(resource_name); + return iter == external_database_info.end() ? nullptr : iter->second; + } + + void refresh_external_database_info(); + + Psc::JSON toJson() { + refresh_external_database_info(); + Psc::JSON ret = Psc::JSON::object(); + Ret_J(icao) + ret.append({"每秒位置数", get_pest()}); + ret.append({"飞机位置轨迹点数", air_pos_track_list.size()}); + ret.append({"地面位置轨迹点数", surface_pos_track_list.size()}); + ret.append({"信号强度", signal_level}); + auto opt_pos = air_pos_track_list.last(); + ret.append({"当前位置消息", opt_pos.has_value() ? opt_pos->to_json() : ""}); + + auto cpr_state_opt = BDS05_airborne_position.value; + ret.append({"cpr解码器状态", + cpr_state_opt.has_value() + ? cpr_state_opt.value().current_state_json() + : ""}); + ret.append({"时间", Psc::utc_2_local_time(timestamp)}); + ret.append({"多点定位数据源", mlat}); + + auto external_info = Psc::JSON::object(); + for (const auto& [resource_key, row] : external_database_info) { + external_info.append({resource_key, row->to_json()}); + } + for (const auto& [resource_key, row] : callsign_external_database_info) { + external_info.append({resource_key, row->to_json()}); + } + ret.append({"external_database_info", external_info}); + + push_all_bds_info(ret); + return ret; + } + +#ifdef Cache_Some_Mode_S + std::list> cache; + size_t total_num = 0; + void add_msg_limit(const std::shared_ptr& msg) { + if (cache.size() > Cache_Some_Mode_S_Num) { + cache.pop_front(); + } + cache.emplace_back(msg); + total_num++; + } +#endif +}; diff --git a/module/Local_Server/Aircraft/Flight_VTO.cpp b/module/Local_Server/Aircraft/Flight_VTO.cpp new file mode 100644 index 0000000..e5c1cde --- /dev/null +++ b/module/Local_Server/Aircraft/Flight_VTO.cpp @@ -0,0 +1 @@ +#include "../Aircraft/Flight_VTO.h" diff --git a/module/Local_Server/Aircraft/Flight_VTO.h b/module/Local_Server/Aircraft/Flight_VTO.h new file mode 100644 index 0000000..d7f7021 --- /dev/null +++ b/module/Local_Server/Aircraft/Flight_VTO.h @@ -0,0 +1,67 @@ +#pragma once +#include "global.h" + + +struct Flight_VTO { + std::string icao; // 飞机身份标识(四字节十六进制代码) + time_t time_stamp{}; // 时间(原始时间数据,可能需要翻译显示) + bool mlat; + RET pos; // 纬度(飞机的当前位置纬度) + RET track; // 航迹(航迹方向,单位:度) + RET speed; // 航速(单位:米/秒) + RET vert_speed; // 航速(单位:米/秒) + RET altitude; // 米 + RET call_sign; + RET squawk; + + + Psc::JSON toJson() { + auto ret = Psc::JSON::object(); + ADD_Json(icao); + ADD_Json(mlat); + ret.children.emplace_back("time_stamp", timeToString(time_stamp)); + if (pos.has_value()) + { + ret.children.emplace_back("latitude", pos.value().lat); + ret.children.emplace_back("longitude", pos.value().lon); + } else + { + ret.children.emplace_back("latitude", nullptr); + ret.children.emplace_back("longitude", nullptr); + } + ADD_Json_RET(track); + ADD_Json_RET(speed); + ADD_Json_RET(altitude); + ADD_Json_RET(vert_speed); + ADD_Json_RET(call_sign); + ADD_Json_RET(squawk); + return ret; + } + + std::string timeToString(time_t time_stamp) { + struct tm* utc_time = std::gmtime(&time_stamp); // 转换为 UTC 时间 + char buffer[80]; + + // 使用 strftime 格式化时间为字符串 + std::strftime(buffer, sizeof(buffer), "%H:%M:%S", utc_time); + + return std::string(buffer); // 返回格式化后的时间字符串 + } + static Flight_VTO to_VTO(SSR::Aircraft_Info* info) { + Flight_VTO vto; + vto.icao = info->icao; + vto.mlat = info->mlat; + vto.time_stamp = info->timestamp; + vto.speed = info->speed(); + // 这个地 是相对于地的意思 + vto.track = info->surface_magnetic_heading(); + // vto.pos = info->pos(); + vto.pos = info->air_pos_track_list.last(); + vto.altitude = info->altitude_meter(); + vto.vert_speed = info->vertical_rate(); + vto.call_sign = info->bds20_call_sign(); + vto.squawk = info->squawk(); + return vto; + } +}; + diff --git a/module/Local_Server/Aircraft/aircraftlist_json.h b/module/Local_Server/Aircraft/aircraftlist_json.h new file mode 100644 index 0000000..79a187b --- /dev/null +++ b/module/Local_Server/Aircraft/aircraftlist_json.h @@ -0,0 +1,260 @@ +#pragma once + +#include "../Data_Source/Database.h" + +// https://wiki.jetvision.de/wiki/Radarcape:Software_Features#Aircraft_List_JSON_Service +struct Aircraft_List_JSON_Service_O { + size_t times; //自定义添加出现的次数 用于显示的优先级 + time_t uti{}; // Time of last message: Seconds since UNIX epoch + std::uint32_t ns{}; // Time of last message: Nanoseconds since Second epoch + std::uint32_t day_second{}; // Time of last message: Nanoseconds since Second epoch + std::string hex; // ICAO Mode-S or c, in Hex format + RET fli; // Flight Identification + RET ava; // Flight data: available flight data sources + std::string src; // Flight data: Source + std::string cla; // Subclass of ADS-B data + RET lat; // Flight data: Latitude (-90 to 90º) + RET lon; // Flight data: Longitude (-180 to 180º) + RET alt; // Flight data: Barometric uncalibrated altitude (ft) + RET spd; // Flight data: Ground Speed (kt) + RET trk; // Flight data: True track (0-360º) + RET vrt; // Flight data: Vertical Rate (ft/min) + std::string gda; // Airborne/Ground + std::string cat; // Aircraft category + RET org; // Origin DB 起点机场 + RET dst; // Destination DB 目标机场 + RET opr; // Operator DB + RET typ; // Type DB + RET reg; // Registration DB + RET squ; // Mode-A (Squawk) + RET cou; // Country + RET dis; // Distance (km or NM depending on configuration) + RET tru; // Trust count Number of Mode-S frames successfully decoded from this A/C. Statistically it's possible that for a very low count the A/C does not actually exist + int dbm{}; // Approximate signal level in dBm; + RET lla; // int Age of last Position (s) + RET tmp; // Temperature (ºC) + RET wsp ; // Wind Speed (kt) + RET wdi; // Wind from Direction (0-360º) + RET mop; // ADS-B MOPS version; + RET spi; // SPI (Squawk ident); + RET alr; // Alert status + RET ias; // IAS + RET tas; // TAS + RET hdgm; // Magnetic Heading + RET hdgt; // True Heading + RET qnhs; // Selected QNH + RET alts; // Selected QNH + RET hdgs; // Selected Heading + RET altg; // GNSS elipsoidal altitude (ft) + RET pic; // Asterix PIC + RET tcm; // ACAS/TCAS status + RET ape; // Autopilot engaged + RET sil; // ADS-B SIL + RET sda; // ADS-B SDA + RET nacp; // ADS-B SDA + RET pest; // Number of position estimations per second + RET nocl; // Number of sensors providing data aircraft + RET tq; // Time quality of this entry + + Psc::JSON toJson() { + auto ret = Psc::JSON::object(); + ADD_Json(times); + ADD_Json(uti); + ADD_Json(ns); + ADD_Json(day_second); + ADD_Json(hex); + ADD_Json_RET(fli); + ADD_Json_RET(ava); + ADD_Json(src); + ADD_Json(cla); + ADD_Json_RET(lat); + ADD_Json_RET(lon); + ADD_Json_RET(alt); + ADD_Json_RET(spd); + ADD_Json_RET(trk); + ADD_Json_RET(vrt); + ADD_Json(gda); + ADD_Json(cat); + ADD_Json_RET(org); + ADD_Json_RET(dst); + ADD_Json_RET(opr); + ADD_Json_RET(typ); + ADD_Json_RET(reg); + ADD_Json_RET(squ); + ADD_Json_RET(cou); + ADD_Json_RET(dis); + ADD_Json(tru); + ADD_Json(dbm); + ADD_Json_RET(lla); + ADD_Json_RET(tmp); + ADD_Json_RET(wsp); + ADD_Json_RET(wdi); + ADD_Json_RET(mop); + ADD_Json_RET(spi); + ADD_Json_RET(alr); + ADD_Json_RET(ias); + ADD_Json_RET(tas); + ADD_Json_RET(hdgm); + ADD_Json_RET(hdgt); + ADD_Json_RET(qnhs); + ADD_Json_RET(alts); + ADD_Json_RET(pic); + ADD_Json_RET(tcm); + ADD_Json_RET(ape); + ADD_Json_RET(sil); + ADD_Json_RET(sda); + ADD_Json_RET(nacp); + ADD_Json_RET(pest); + ADD_Json_RET(nocl); + ADD_Json_RET(tq); + return ret; + } + + + // https://wiki.jetvision.de/wiki/Radarcape:Software_Features#Aircraft_List_JSON_Service + static Aircraft_List_JSON_Service_O to_That(DataBase* db, Aircraft* info) { + Aircraft_List_JSON_Service_O ret; + // 航线查询结果保存在 Aircraft 中,仅在首次加载或 callsign 变化时重新读取数据库。 + ret.times = info->times; + ret.uti = info->timestamp; + ret.ns = info->mlat_timestamp.nanosec; + ret.day_second = info->mlat_timestamp.daysec; + ret.hex = info->icao; // TODO not c + // fli Flight Identification Mode-S From ADS-B or BDS2,0 as specified on ICAO Annex 10, Volume IV, Table 3-9; "?" when A/C reports loss of interface + ret.fli = info->bds20_call_sign(); + // ava ava Flight data: available flight data sources One letter per available flight data sources. A=ADSB, M=Jetvision MLAT, F=FlightAware MLAT, O=OGN, L=FLARM + // ret.ava + ret.ava = "暂不支持"; + // ret.src + ret.src = "暂不支持"; + // ret.cla + ret.cla = "暂不支持"; + RET pos = info->pos(); + if(pos.has_value()) { + ret.lat = pos.value().lat; + ret.lon = pos.value().lon; + } + ret.alt = info->altitude_meter(); + ret.spd = info->speed(); + ret.trk = info->air_magnetic_heading(); + ret.vrt = info->vertical_rate(); + // 飞机在地面还是空中 G for on ground, A for airborne (if TIS-B then lowercase g or a) + ret.gda = info->is_on_ground() ? "G" : "A"; + auto vt = info->vortex_type(); + if (vt.has_value()) { + ret.cat = Vortex_Type_to_radarcape_code(vt.value()); + } + // =============== 来自DB 航班数据库 ================ + auto route_info = info->external_database_row("vrs_routes"); + if (!route_info) { + route_info = info->external_database_row("adsblol_vrs_standing_data_routes"); + } + if (!route_info) { + route_info = info->external_database_row("vradarserver_standing_data_routes"); + } + if (route_info) { + const auto airport_codes = route_info->get("AirportCodes"); + if (airport_codes && !airport_codes->empty()) { + const auto first_separator = airport_codes->find('-'); + const auto last_separator = airport_codes->rfind('-'); + ret.org = airport_codes->substr(0, first_separator); + ret.dst = last_separator == std::string::npos + ? *airport_codes + : airport_codes->substr(last_separator + 1); + } + } + //ret.opr + const auto opensky_info = + info->external_database_row("opensky_aircraft_database"); + if (opensky_info) { + ret.opr = opensky_info->get("operator"); + } + //ret.typ + if (opensky_info) { + ret.typ = opensky_info->get("typecode"); + } + //ret.reg + if (opensky_info) { + ret.reg = opensky_info->get("registration"); + } + // =============================== + // squ Mode-A (Squawk) Mode-S While this is a Mode-A code, + // it can only be correlated in Mode-S targets. Mode-A or Mode-A/C targets are not + // displayed in decoded formats since they cannot be correlated with a value + ret.squ = info->squawk(); + // cou Country Country according to Mode-S allocation block if decodable, mostly unusable/invalid for FLARM A/C + //ret.cou + const auto faa_info = info->external_database_row("faa_aircraft_registry"); + if (faa_info) { + ret.cou = faa_info->get("country"); + } + // dis Distance (km or NM depending on configuration) To the Receiver GNSS location + auto p = db->base_station.get_pos(); + if (p.has_value() && pos.has_value()) { + ret.dis = SSR::CPR::haversine(pos.value(), p.value()); + } + ret.tru = info->times; + ret.dbm = info->signal_level; + //ret.lla + ret.lla = std::time(nullptr) - info->timestamp; + //ret.tmp + ret.tmp = info->temperature(); + //ret.wsp + ret.wsp = info->wind_speed(); + //ret.wdi + ret.wdi = info->wind_direction(); + ret.mop = info->ADS_B_Ver(); + //ret.spi + ret.spi = info->squawk(); + //ret.alr + ret.alr = info->alart(); + auto air_speed = info->get_airspeed(); + if (air_speed.has_value()) { + auto& t = air_speed.value(); + auto type = t.get_airspeed_type(); + if (type == SSR::Airspeed_type::Indicated_airspeed_IAS) { + ret.ias = air_speed.value().get_speed(); + } + if (type == SSR::Airspeed_type::True_airspeed_TAS) { + ret.tas= air_speed.value().get_speed(); + } + ret.hdgm = air_speed.value().get_magnetic_heading(); + ret.hdgt = "暂不支持"; + } + //ret.qnhs Selected QNH + ret.qnhs = "暂不支持"; + //ret.alts Selected Altitude + ret.alts = "暂不支持"; + //ret.hdgs Selected Heading + ret.hdgs = info->select_heading(); + auto it = info->get_speed_base(); + if (it.has_value()) { + auto i = it.value(); + i.get_GNSS_barometric_altitudes_difference(); + auto diff = i.get_GNSS_barometric_altitudes_difference(); + if (diff.has_value() && ret.alt.has_value()) { + ret.altg = diff.value() + ret.alt.value(); + } + } + //ret.pic + ret.pic = "暂不支持"; + //ret.tcm + ret.tcm = "暂不支持"; + // ape Autopilot engaged 2 Mode-S + //ret.ape + ret.ape = "暂不支持"; + ret.sil = info->SIL(); + //ret.sda + ret.sda = "暂不支持"; + ret.nacp = info->NACp(); + // pest Number of position estimations per second MLAT + ret.pest = info->get_pest(); + // nocl Number of sensors providing data aircraft MLAT + ret.nocl = 1; + // tq Time quality of this entry Sensor 0: GNSS 1: Sensor NTP 2: Server NTP + //ret.tq + ret.tq = "暂不支持"; + return ret; + } +}; + diff --git a/module/Local_Server/Aircraft/global.h b/module/Local_Server/Aircraft/global.h new file mode 100644 index 0000000..7517a08 --- /dev/null +++ b/module/Local_Server/Aircraft/global.h @@ -0,0 +1,38 @@ +#pragma once +#include "../../../../../core_library/Core/Core/Base/JSON.h" +#include "Core/socket/export.h" +#include "SSR/export.h" +#include +#include +#include +#include +#include +#include +#include + +// 用于debug 散乱的飞机从何而来 +// #define Cache_Some_Mode_S +#ifdef Cache_Some_Mode_S + #define Cache_Some_Mode_S_Num 20 +#endif + + + +#define Parse(t, Field) \ +auto& _##t = info.Field.value; \ +info.Field.time = info.timestamp; \ +if (!_##t.has_value()) _##t = std::remove_reference_t::ValueType(); \ +_##t.value().parse(msg); \ +auto& t = _##t.value(); +// ret.append(NAME.value.has_value()? Psc::Json(STRINGIFY(NAME##_time), NAME.time) : Psc::Json(STRINGIFY(NAME##_time), nullptr)); +#define STRINGIFY(x) #x +#define ADD_Json(NAME) ret.children.emplace_back(STRINGIFY(NAME), NAME); +#define ADD_Json_RET(NAME) \ +if (NAME.has_value()) \ +{ \ +ret.children.emplace_back(STRINGIFY(NAME), NAME.value()); \ +} else \ +{ \ +ret.children.emplace_back(STRINGIFY(NAME), nullptr); \ +} + diff --git a/module/Local_Server/Crawler/Crawler.cpp b/module/Local_Server/Crawler/Crawler.cpp new file mode 100644 index 0000000..c8ccda9 --- /dev/null +++ b/module/Local_Server/Crawler/Crawler.cpp @@ -0,0 +1,96 @@ +#include "Crawler.h" + +#include "Local_Server/server/Global.h" + +void Crawler::handle(Call_back call_back) { + using namespace drogon; + int idx = 0; + + SQLite::Statement aircraft_rows( + db_, "SELECT icao24, type_code FROM mictronics_aircraft_database"); + while (aircraft_rows.executeStep()) { + idx++; + + if (idx > 10) break; + if (idx % 1 == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + { + static Frequency_Limit fl; + if (fl.test()) { + std::cout << idx++ << "/618325 条数据已发出! " << std::endl; + } + } + const auto hex = aircraft_rows.getColumn(0).getString(); + const auto icaoType = aircraft_rows.isColumnNull(1) + ? std::string{} + : aircraft_rows.getColumn(1).getString(); + + bool ok = check_aircraft_exists(db_, hex); + if (!ok) { + static Frequency_Limit fl; + if (fl.test()) { + std::cout << idx << "/618325 数据存在跳过! " << std::endl; + } + } + auto client = HttpClient::newHttpClient("https://planespotters.live"); + + std::string path = "/api/aircraft/hex/" + hex; + + // GET https://planespotters.live/api/aircraft/hex/780F0E + // User-Agent: IntelliJ HTTP Client/CLion 2025.2.6 + // Accept-Encoding: br, deflate, gzip, x-gzip + // Accept: */* + + auto req = HttpRequest::newHttpRequest(); + req->setMethod(Get); + req->setPath(path); + + // query 参数:?icaoType=B763 + req->setParameter("icaoType", icaoType); + + req->addHeader("Accept-Language", "en-US,en;q=0.9"); + req->addHeader("User-Agent", "IntelliJ HTTP Client/CLion 2025.2.6"); + req->addHeader("Accept", "*/*"); + req->addHeader("Accept-Encoding", "br, deflate, gzip, x-gzip"); + + + + + + client->sendRequest(req, [hex, icaoType, call_back]( + ReqResult r, const HttpResponsePtr &resp) { + if (r != ReqResult::Ok || !resp) { + LOG_ERROR << "request failed, ReqResult=" << (int)r; + return; + } + + if (resp->getStatusCode() != k200OK) { + static Frequency_Limit_Multi fl; + auto tt = to_string(resp->getStatusCode()); + if (fl.test(tt)) { + std::cout << tt << std::endl; + + } + + return; + } + + // 解析 JSON(如果响应是 JSON) + auto json = resp->getJsonObject(); + if (!json) { + LOG_ERROR << "not json, body=" << resp->getBody(); + return; + } + + auto t = try_parse_json(json->toStyledString()); + + if (!t) { + std::cout << "解析失败???" << t->to_json_string() << std::endl; + Psc::fail_fast_core_dump(); + return; + } + call_back(hex, icaoType, t.value()); + }); + } +} diff --git a/module/Local_Server/Crawler/Crawler.h b/module/Local_Server/Crawler/Crawler.h new file mode 100644 index 0000000..8a9705d --- /dev/null +++ b/module/Local_Server/Crawler/Crawler.h @@ -0,0 +1,96 @@ +#pragma once + +#include "../../../../../core_library/Core/Core/Base/JSON.h" +#include "Core/Statistics/Frequency_Limit.h" + +#include +#include +#include +#include +#include +#include + + +class Crawler { +public: + using Call_back = std::function; + + explicit Crawler(const std::string &db_path) + : db_(db_path, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE) { + aircraft_json_table_create_if_not_exists(); + // 可选:性能更好(仍然是同步) + db_.exec("PRAGMA journal_mode=WAL;"); + db_.exec("PRAGMA synchronous=NORMAL;"); + } + + void aircraft_json_table_create_if_not_exists() { + db_.exec(R"sql( + CREATE TABLE IF NOT EXISTS aircraft_json ( + icao TEXT PRIMARY KEY, + json TEXT + ); + )sql"); + } + + bool check_aircraft_exists(SQLite::Database& db, const std::string& icao) { + // 查询是否存在该icao + static auto stmt_check_ = SQLite::Statement(db, R"sql( + SELECT COUNT(*) FROM aircraft_json WHERE icao = ?; + )sql"); + + stmt_check_.reset(); + stmt_check_.clearBindings(); + stmt_check_.bind(1, icao); + + int count = 0; + if (stmt_check_.executeStep()) { + count = stmt_check_.getColumn(0).getInt(); + } + + // 如果存在记录,返回 true + return count > 0; + } + + void insert_aircraft_json(const std::string &icao, const Psc::JSON &j) { + static auto stmt_upsert_ = SQLite::Statement(db_, R"sql( + INSERT INTO aircraft_json(icao, json) + VALUES(?, ?) + ON CONFLICT(icao) DO UPDATE SET + json = excluded.json; + )sql"); + const std::string json_str = j.to_json_string(); + std::lock_guard g(mtx); + SQLite::Transaction tx(db_); // BEGIN + stmt_upsert_.reset(); + stmt_upsert_.clearBindings(); + stmt_upsert_.bind(1, icao); + stmt_upsert_.bind(2, json_str); + stmt_upsert_.exec(); + tx.commit(); + } + + void req_all_insert_aircraft_json() { + std::atomic idx = 0; + handle( + [this, &idx](std::string hex, std::string icaoType, Psc::JSON &json) { + // 直接同步写库(最简单) + try { + static Frequency_Limit fl; + idx++; + if (fl.test()) { + std::cout << idx << "/618325 条数据处理成功! " << std::endl; + } + insert_aircraft_json(hex, json); + } catch (const std::exception &e) { + // 用你自己的 LOG_ERROR + std::cerr << "sqlite insert failed: " << e.what() << "\n"; + } + }); + } + +private: + SQLite::Database db_; + std::mutex mtx; + void handle(Call_back cb); // 你自己实现/已有 +}; diff --git a/module/Local_Server/DSP/DSP_Config.cpp b/module/Local_Server/DSP/DSP_Config.cpp new file mode 100644 index 0000000..c53996a --- /dev/null +++ b/module/Local_Server/DSP/DSP_Config.cpp @@ -0,0 +1,382 @@ + + +#include "Local_Server/DSP/DSP_Config.h" +#include "../server/Global.h" +#include "Core/socket/ByteOrder.h" + +#include +#include +#include + +#if!USE_PROCESS + +// adsb_set_less_30Mhz_ddc_param_t set_less_30Mhz_ddc_param; +// adsb_set_less_30Mhz_fft_param_t set_less_30Mhz_fft_param; + + +decltype(&adsb_set_less_30Mhz_ddc_param) psc_set_less_30Mhz_ddc_param; +decltype(&adsb_set_less_30Mhz_fft_param) psc_set_less_30Mhz_fft_param; + +#endif + + +using namespace drogon; +// ws://127.0.0.1:8848/chat + + + + + + + + + + + +BaseLogger* dsp_logger = nullptr; +DSP_Config::~DSP_Config() = default; + +void DSP_Config::close_dsp_device() const { + + auto r = Psc::try_load_function(lib, "adsb_close"); + if (!r) { + Psc::fail_fast(); + } + auto tmp_adsb_close = (decltype(&adsb_close)) r.value(); + std::ostringstream oss; + oss << "stop_program ===== " << VAR_STR_1(stop_program) << std::endl; + oss << "DSP_Config adsb_close 开始关闭子库!" << std::endl; + tmp_adsb_close(); + oss << "DSP_Config adsb_close 关闭子库结束!" << std::endl; + dsp_logger->c_debug("dsp", {}, oss.str()); +} + + +void DSP_Config::server(Global *g) { + + auto &svr = g->svr; + auto &api = g->api; + std::string name = "dsp"; + + drogon::app().registerWebSocketControllerRegex(R"(^/websocket/.*$)", "DSP_Ws_Mgr", {Get}); + + ws_iq = Ws_Mgr::instance()->register_ws("/download_iq"); + ws_fft = Ws_Mgr::instance()->register_ws("/get_fft"); + + + svr.Post(api + svr.update + name, [this, g](HTTP_Param) { + CHECK_JSON_PARAM + init_update(¶ms); + + +#if USE_PROCESS + adsb_set_less_30Mhz_ddc_param(this->center_freq, this->sample_rate, this->band_width, 1); + adsb_set_less_30Mhz_fft_param(fft_win_type, fft_point_number, fft_period_ms, 1); +#else + psc_set_less_30Mhz_ddc_param(this->center_freq, this->sample_rate, this->band_width, 1); + psc_set_less_30Mhz_fft_param(fft_win_type, fft_point_number, fft_period_ms, 1); +#endif + set_fft_point_number(this->fft_point_number); + g->save(); + res->setBody(warp(to_json()).to_json_string()); + }); + svr.Post(api + "get_dsp_config", [this](HTTP_Param) { res->setBody(warp(to_json()).to_json_string()); }); + + + drogon::app().registerHandler(api + "start_record_iq_stream", + [this](const drogon::HttpRequestPtr &request, + std::function &&callback) { + this->record_iq_stream_ing = true; + auto res = drogon::HttpResponse::newHttpResponse(); + res->setBody("{}"); + callback(res); + }, + {drogon::Post}); + + + svr.Post(api + "end_record_iq_stream", [this](HTTP_Param) { + this->record_iq_stream_ing = false; + res->setBody("{}"); + }); + + svr.Post(api + "restart_device", [this](HTTP_Param) { +#if USE_PROCESS + restart_device(); +#endif + }); +} + + +void DSP_Config::set_fft_point_number(uint64_t new_fft_point_number, bool force) { + fft_point_number = new_fft_point_number; +#if !USE_PROCESS + std::lock_guard lock(fft_mtx); + if (!force && fft_data.size() == (new_fft_point_number * sizeof(short))) { + return; + } + fft_data.resize(new_fft_point_number * sizeof(short)); +#endif + +} + + +#if USE_PROCESS +void DSP_Config::adsb_set_less_30Mhz_ddc_param(double center_freq, double sample_rate, double band_width, int sw) { + JSON ret = JSON::object(); + ret.append(JSON("method", "adsb_set_less_30Mhz_ddc_param")); + ret.append(JSON("center_freq", center_freq)); + ret.append(JSON("sample_rate", sample_rate)); + ret.append(JSON("band_width", band_width)); + ret.append(JSON("sw", sw)); + auto d = ret.to_json_string(); + json_func_call.write((uint8_t *) d.data(), d.size()); +} + +void DSP_Config::adsb_set_less_30Mhz_fft_param(UHD_FFTWin fft_win_type, size_t fft_point_number, size_t fft_period_ms, + int sw) { + JSON ret = JSON::object(); + ret.append(JSON("method", "adsb_set_less_30Mhz_fft_param")); + ret.append(JSON("fft_win_type", fft_win_type)); + ret.append(JSON("fft_point_number", fft_point_number)); + ret.append(JSON("fft_period_ms", fft_period_ms)); + ret.append(JSON("sw", sw)); + auto d = ret.to_json_string(); + json_func_call.write((uint8_t *) d.data(), d.size()); +} + +void DSP_Config::restart_device() { + JSON ret = JSON::object(); + ret.append(JSON("method", "restart_device")); + auto d = ret.to_json_string(); + json_func_call.write((uint8_t *) d.data(), d.size()); +} +#endif + + +#define USE_ERROR_CBK + +void DSP_Config::init_env() { + + set_fft_point_number(fft_point_number, true); + auto g = Global::instance(); + Detach_Thread_Manager &manager = g->thread_manager; + + auto handle_iq= [this](void* data) { + uint8_t* pdata = (uint8_t*) data; + auto wi = Ws_Mgr::instance(); + if (!wi) return; + auto d = (UHD_Struct_KFFT *) (pdata); + if (debug) { + std::ostringstream oss; + oss << "UHD_Struct_KFFT{" << mem2hex(std::string((char *) pdata, 64), true, " ") << std::endl; + oss << "DataType{" << (int) d->DataType << "} 接收到数据:[" + << "] == " << mem2hex(std::string((char *) pdata + 64, 20), true, " ") << std::endl; + server_logger->c_debug({}, {}, oss.str()); + } + if (d->DataType == zd_ddc || d->DataType == kd_ddc) { + auto len = d->UsefullLen; + bool cross_the_boundary = len > 2048; + if (cross_the_boundary || debug) { + auto show_len = (size_t) std::min((decltype(len)) 20, len); + + std::ostringstream oss; + oss << "iq 有效数据长度 【" << len << "】 有效数据: 【" + << std::string((char *) pdata + 64, show_len) << "】" << std::endl; + server_logger->c_debug({}, {}, oss.str()); + } + if (cross_the_boundary) { + std::ostringstream oss; + oss << LOG_POS << "ERROR: ddc iq 回调返回的有效长度越界!" << std::endl; + server_logger->c_debug({}, {}, oss.str()); + return; + } + if (record_iq_stream_ing) { + ws_iq->push_data((const char *) pdata , 2112); + } + } else if (d->DataType == kd_fft) { +#if !USE_PROCESS + auto idx = d->Param.FFT.SubFrame; + auto max = fft_point_number/1024; + if (idx >= max) { + std::ostringstream oss; + oss << "收到了不合法kd_fft Param.FFT.SubFrame " << idx << std::endl; + server_logger->c_debug({}, {}, oss.str()); + return; + } + + + std::lock_guard lock(fft_mtx); + auto pos = fft_data.data() + idx * 2048; + memcpy(pos, pdata + 64, 2048); +#endif + } + }; + +#if USE_PROCESS + manager.test_and_start_thread("读取iq进程数据线程", [this, handle_iq](std::atomic &running) { + while (running) { + uint8_t buf[3000]; + size_t length = 3000; + + while (rb.read(buf, length)) { + Frequency_Limit lm(0.1); + if (lm.test()) { + std::cout << "读取到iq数据" << std::endl; + } + handle_iq(buf); + //auto data = std::string((char *) buf, length); + // if (length != 0) { + // std::cout << "read:【" << length << "】 【" << mem2hex(data.substr(64, 20)) << "】" << std::endl; + // } + if (!running) { + break; + } + } + + std::this_thread::sleep_for(std::chrono::microseconds(1)); + } + }); +#else + manager.test_and_start_thread("iq回调处理线程", [this, handle_iq](std::atomic &running) { + Frequency_Limit_ST limit(frame_rate); + while (running) { + auto list = iq_memory_buffer.get_all(); + if (debug && list.size() > 1000) { + std::ostringstream oss; + oss << "警告: 数据过多 当前数据队列长度:" << list.size() << std::endl; + server_logger->c_debug({}, {}, oss.str()); + } + if (list.empty()) { + std::this_thread::sleep_for(std::chrono::microseconds(1)); + continue; + } + auto wi = Ws_Mgr::instance(); + if (!wi) continue; + auto frame_number = list.size(); + for (int i = 0; i < frame_number; i++) { + auto pdata = list[i].data(); + handle_iq(pdata); + } + } + }); +#endif + + + + + auto interval = 1.0 / (double) frame_rate; + app().getLoop()->runEvery(interval, [this] { + +#if USE_PROCESS + wi->ws_get_fft->push_data((const char *) psm.shm.data(), fft_point_number * 2); +#else + std::lock_guard lock(fft_mtx); + ws_fft->push_data((const char *) fft_data.data(), fft_data.size()); +#endif + + }); + + + + +#if !USE_PROCESS + + { + auto r = try_load_library(Psc::get_abs_path(library_path)); + if (!r) { + Psc::fail_fast(); + } + lib = r.value(); + } + { + auto r = Psc::try_load_function(lib, "adsb_set_less_30Mhz_ddc_param"); + if (!r) { + Psc::fail_fast(); + } + psc_set_less_30Mhz_ddc_param = (adsb_set_less_30Mhz_ddc_param_t)r.value(); + } + { + auto r = Psc::try_load_function(lib, "adsb_set_less_30Mhz_fft_param"); + if (!r) { + Psc::fail_fast(); + } + psc_set_less_30Mhz_fft_param = (adsb_set_less_30Mhz_fft_param_t)r.value(); + } + using set_iq_cbk_t = decltype(&adsb_set_iq_cbk); + set_iq_cbk_t set_iq_cbk; + { + auto r = Psc::try_load_function(lib, "adsb_set_iq_cbk"); + if (!r) { + Psc::fail_fast(); + } + set_iq_cbk = (set_iq_cbk_t)r.value(); + } + + using adsb_set_error_cbk_t = decltype(&set_error_cbk); + adsb_set_error_cbk_t adsb_set_error_cbk; + { + auto r = Psc::try_load_function(lib, "set_error_cbk"); + if (!r) { + Psc::fail_fast(); + } + adsb_set_error_cbk = (adsb_set_error_cbk_t)r.value(); + } + + + psc_set_less_30Mhz_ddc_param(center_freq, sample_rate, band_width, 1); + psc_set_less_30Mhz_fft_param(fft_win_type, fft_point_number, fft_period_ms, 1); + + + set_iq_cbk( + [](void *phandle, void *pdata, uint64_t length) { + if (stop_program != 0) { + static Frequency_Limit fl; + if (fl.test()) { + std::ostringstream oss; + oss << "set_iq_cbk 回调退出:" << signal_to_string(stop_program) << " " << VAR_STR_1(stop_program) << LOG_POS << std::endl; + dsp_logger->c_debug({}, {}, oss.str()); + } + } + if (length != 2112) { + std::ostringstream oss; + oss << LOG_POS << " IQ回调传入的长度不对!" << std::endl; + dsp_logger->c_debug({}, {}, oss.str()); + return; + } + auto that = (DSP_Config *) phandle; + that->iq_memory_buffer.push(pdata, length); // length 永远等于2112 + }, + (void*)&Global::instance()->dsp_config); + + +#ifdef USE_ERROR_CBK + + adsb_set_error_cbk([](const char *file_name ,const char *func_name,const char *err_msg) { + if (stop_program != 0) { + static Frequency_Limit fl; + if (fl.test()) { + std::ostringstream oss; + oss << "adsb_set_error_cbk 回调退出:" << signal_to_string(stop_program) << " " << VAR_STR_1(stop_program) << LOG_POS << std::endl; + dsp_logger->c_debug({}, {}, oss.str()); + } + return; + } + Log_Type lt{ + {}, + { + {"file_name", std::string(file_name)}, + {"func_name", std::string(func_name)}, + } + }; + dsp_logger->debug({}, lt, std::string(err_msg)); + }); +#endif + + + +#else + adsb_set_less_30Mhz_ddc_param(center_freq, sample_rate, band_width, 1); + adsb_set_less_30Mhz_fft_param(fft_win_type, fft_point_number, fft_period_ms, 1); +#endif + +} diff --git a/module/Local_Server/DSP/DSP_Config.h b/module/Local_Server/DSP/DSP_Config.h new file mode 100644 index 0000000..33c34e4 --- /dev/null +++ b/module/Local_Server/DSP/DSP_Config.h @@ -0,0 +1,132 @@ +#ifndef PLANTSUNCAT_DSP2_H +#define PLANTSUNCAT_DSP2_H + +#include "../server/WebSocket_Manager.h" +#include "global.h" + +class Global; + + +#define USE_PROCESS 0 +#ifdef USE_PROCESS +#include "../../../../../core_library/Core/Core/Shared_Memory/Shared_Memory.h" +#endif +// #ifdef WIN32 +// #define USE_PROCESS 1 +// #endif +// #define USE_PROCESS 1 + +enum Demod_Mode { + LSB, + USB, + AM, + FM +}; + + +extern BaseLogger* dsp_logger; +struct DSP_Config : Base_DSP { + std::string url; + std::uint16_t port{}; + std::int64_t power_min; + std::int64_t power_max; + std::int64_t power_offset; + std::uint64_t frame_rate; + bool debug; + Demod_Mode demod_mode; + std::int64_t audio_gain; + std::int64_t color_gain; + ~DSP_Config(); + + void close_dsp_device() const; + + void init_update(const Psc::JSON *that_json) { + Get_J(port); + Get_J(center_freq); + Get_J(band_width); + Get_J(sample_rate); + Get_J(fft_point_number); + Get_J(power_min); + Get_J(power_max); + Get_J(power_offset); + Get_J(demod_mode); + Get_J(audio_gain); + Get_J(color_gain); + } + void init(const Psc::JSON *that_json) { + init_update(that_json); + Get_J(url); + Get_J(fft_period_ms); + Get_J(fft_win_type); + Get_J(debug); + Get_J(frame_rate); + Get_J(library_path); + } + Psc::JSON to_json() const { + Psc::JSON ret = Base_DSP::to_json(); + Ret_J(url); + Ret_J(port); + Ret_J(power_min); + Ret_J(power_max); + Ret_J(power_offset); + Ret_J(frame_rate); + Ret_J(debug); + Ret_J(demod_mode); + Ret_J(audio_gain); + Ret_J(color_gain); + return ret; + } + + + void init_env(); + + + void set_fft_point_number(uint64_t fft_point_number, bool force = false); + + + + std::atomic record_iq_stream_ing = false; + + struct Memory_Buffer { + std::vector get_all() { + std::vector empty; + std::lock_guard lock(m); + std::swap(empty, data); + return empty; + } + void push(void *pdata, uint64_t length) { + std::lock_guard lock(m); + data.emplace_back((char *) pdata, length); + } + + protected: + std::vector data; + Psc::Spin_Lock m; + }; + +#if USE_PROCESS + + + void adsb_set_less_30Mhz_ddc_param(double center_freq, double sample_rate, double band_width, int sw); + void adsb_set_less_30Mhz_fft_param(UHD_FFTWin fft_win_type, size_t fft_point_number, size_t fft_period_ms, int sw); + + Psc::SM_Block_RingBuffer json_func_call = Psc::SM_Block_RingBuffer("json_func_call2", 1000); + Psc::SM_Block_RingBuffer rb = Psc::SM_Block_RingBuffer("rb", 2112 * 1000); + Psc::Pure_Share_Memory psm = Psc::Pure_Share_Memory("fft_data", 65536); + void restart_device(); +#else + + + void server(Global *g); + std::shared_ptr ws_fft; + std::shared_ptr ws_iq; + Memory_Buffer iq_memory_buffer; + std::vector fft_data; + std::mutex fft_mtx; + void* lib{}; +#endif + +}; + + +#endif diff --git a/module/Local_Server/DSP/export.h b/module/Local_Server/DSP/export.h new file mode 100644 index 0000000..a5d1203 --- /dev/null +++ b/module/Local_Server/DSP/export.h @@ -0,0 +1,3 @@ +#pragma once + +#include "DSP_Config.h" \ No newline at end of file diff --git a/module/Local_Server/DSP/global.h b/module/Local_Server/DSP/global.h new file mode 100644 index 0000000..a987d3f --- /dev/null +++ b/module/Local_Server/DSP/global.h @@ -0,0 +1,34 @@ +#pragma once +#include "../../dll_source/main.h" +#include "Core/spdlog/export.h" +#include "Core/Base/JSON.h" + +struct Base_DSP { + std::string library_path; + std::uint64_t center_freq{}; + std::uint64_t band_width{}; + std::uint64_t sample_rate{}; + std::atomic fft_point_number = 1024; + int fft_period_ms; + UHD_FFTWin fft_win_type; + [[nodiscard]] Psc::JSON to_json() const { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(library_path); + Ret_J(center_freq); + Ret_J(band_width); + Ret_J(sample_rate); + Ret_J(fft_point_number); + Ret_J(fft_period_ms); + Ret_J(fft_win_type); + return ret; + } + void load(Psc::JSON* that_json) { + Get_J(library_path); + Get_J(center_freq); + Get_J(band_width); + Get_J(sample_rate); + Get_J(fft_point_number); + Get_J(fft_period_ms); + Get_J(fft_win_type); + } +}; diff --git a/module/Local_Server/Data_Feed/Data_Feed.cpp b/module/Local_Server/Data_Feed/Data_Feed.cpp new file mode 100644 index 0000000..a5d0402 --- /dev/null +++ b/module/Local_Server/Data_Feed/Data_Feed.cpp @@ -0,0 +1,177 @@ +#include "Local_Server/Data_Feed/Data_Feed.h" +#include "../server/Global.h" +void Data_Feed_UDP_Server::handle_in_loop() { + svr.tick(); +} +bool Data_Feed_UDP_Server::_open() { + svr.set_bind_address("0.0.0.0", port); + svr.create(); + + server_logger->c_debug({}, {}, to_string() + "开启!"); + return true; +} +JSON Data_feed_Config::get_feed(const std::string& key) { + auto ret = map.get(key); + if (ret.has_value()) { + auto r = ret.value().get(); + return r->to_json(); + } + return {nullptr}; +} + +void Data_feed_Config::server(Global* g) { + auto& svr = g->svr; + auto& api = g->api; + std::string name = "data_feed"; + + + svr.Post(api + "get_data_feed_state", [this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(key, params.try_get_string("key")) + auto t = map.get(key); + JSON ret{nullptr}; + if (t.has_value()) { + ret = t.value()->get_state_json(); + } + res->setBody(warp(ret).to_json_string()); + }); + + + + svr.Post(api + "update_data_feed_config", [this](HTTP_Param) { + CHECK_JSON_PARAM + Get_J(packet_byte_size) + Get_J(empty_wait_milliseconds) + res->setBody(warp(to_json()).to_json_string()); + Global::save(); + }); + + svr.Post(api + "get_data_feed_config", [this](HTTP_Param) { + res->setBody(warp(to_json()).to_json_string()); + }); + + svr.Post(api + "get_data_feed_server_connect", [this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(key, params.try_get_string("key")) + auto t = map.get(key); + JSON ret{nullptr}; + if (t.has_value()) { + auto dfs = dynamic_cast(t.value().get()); + if (dfs) { + ret = dfs->get_clients_json(); + } + auto dfs2 = dynamic_cast(t.value().get()); + if (dfs2) { + ret = dfs2->get_clients_json(); + } + } + res->setBody(warp(ret).to_json_string()); + }); + + svr.Post(api + svr.search + name, [this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(type, params.try_get_string("key")) + res->setBody(warp(get_feed(type)).to_json_string()); + }); + + + + svr.Post(api + svr.update + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(key, params.try_get_string("key")) + HTTP_REQUIRE_VALUE(t, map.get(key)) + // 直接关闭 + t->close(); + t->from_json(¶ms); + if (t->enable) { + t->check_and_open(); + } + Global::save(); + g->mode_acs.source_feed_relation_config.set_need_refresh(); + }); + + svr.Post(api + svr.insert + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_PTR(data, params.get("data")) + HTTP_REQUIRE_VALUE(t, data->try_get_string("type")) + auto df = create_data_feed_from_type(t); + df->from_json(data); + HTTP_REQUIRE_VALUE(enable, data->try_get_bool("enable")) + if (enable) + { + df->check_and_open(); + } + HTTP_REQUIRE_TRUE(map.insert(index, df), "index") + res->setBody(warp(to_json()).to_json_string()); + Global::save(); + g->mode_acs.source_feed_relation_config.set_need_refresh(); + }); + + svr.Post(api + svr.remove + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_VALUE(df, map.try_get(index)) + df->close(); + HTTP_REQUIRE_TRUE(map.remove(index), "index") + g->save(); + res->setBody(warp(to_json()).to_json_string()); + g->mode_acs.source_feed_relation_config.set_need_refresh(); + }); + + svr.Post(api + svr.rise + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_TRUE(map.swap(index, index - 1), "index") + g->save(); + res->setBody(warp(to_json()).to_json_string()); + g->mode_acs.source_feed_relation_config.set_need_refresh(); + }); + svr.Post(api + svr.fall + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_TRUE(map.swap(index, index + 1), "index") + g->save(); + res->setBody(warp(to_json()).to_json_string()); + g->mode_acs.source_feed_relation_config.set_need_refresh(); + }); +} + +void BIN_Msg_Buffer::push(const std::string& msg) { + auto output_packet_size = Global::instance()->mode_acs.data_feed_config.packet_byte_size.load(); + std::lock_guard g(mtx); + auto& pool = Global::instance()->mode_acs.data_feed_config.pool_; + if (msg_list_cache.empty() || msg_list_cache.back()->size() > output_packet_size) { + auto t = pool.get(); + t->append(msg); + msg_list_cache.push_back(t); + } else { + msg_list_cache.back()->append(msg); + } +} +const std::vector &BIN_Msg_Buffer::get_all() { + std::lock_guard g(mtx); + std::swap(msg_list_cache, msg_list); + msg_list_cache.clear(); + mode_s_msg_num = 0; + mode_other_msg_num = 0; + return msg_list; +} +BIN_Msg_Buffer::BIN_Msg_Buffer() { + msg_list_cache.reserve(8 * 1024); + msg_list.reserve(8* 1024); +} +Psc::JSON BIN_Msg_Buffer::state_json() { + + auto output_packet_size = Global::instance()->mode_acs.data_feed_config.packet_byte_size.load(); + size_t size, cache_size, pool_capacity, pool_free_count; + { + std::lock_guard g(mtx); + auto& pool = Global::instance()->mode_acs.data_feed_config.pool_; + size = msg_list.size(); + cache_size = msg_list_cache.size(); + pool_capacity = pool.capacity(); + pool_free_count = pool.free_count(); + } + return VAR_JSON_7(mode_s_msg_num, mode_other_msg_num, output_packet_size, cache_size, size, pool_capacity, pool_free_count); +} diff --git a/module/Local_Server/Data_Feed/Data_Feed.h b/module/Local_Server/Data_Feed/Data_Feed.h new file mode 100644 index 0000000..b54ff23 --- /dev/null +++ b/module/Local_Server/Data_Feed/Data_Feed.h @@ -0,0 +1,356 @@ +#pragma once + +#include "global.h" + +enum class Output_Data_Format { + AVR, + AVR_MLAT, + BIN, + SBS, + BIN_ID +}; + +enum class Mode_S_Output_Type { + DF_11_17_18, + NO_POS_Mode_S, + ALL_Mode_S +}; + +struct Output_Format { + Output_Data_Format type = Output_Data_Format::BIN; + std::atomic use_status = false; + Mode_S_Output_Type mode_s_output_type = Mode_S_Output_Type::DF_11_17_18; + std::atomic use_mode_ac = false; + std::atomic sbs_only_pos = false; + std::atomic use_crc = false; + + [[nodiscard]] Psc::JSON to_Json() const { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(type) + Ret_J(use_status) + Ret_J(mode_s_output_type) + Ret_J(use_mode_ac) + Ret_J(sbs_only_pos) + return ret; + } + + void from_json(const Psc::JSON *that_json) { + Get_J(type) + Get_J(use_status) + Get_J(mode_s_output_type) + Get_J(use_mode_ac) + Get_J(sbs_only_pos) + } +}; + + +class Data_Feed { +public: + virtual ~Data_Feed() = default; + BIN_Msg_Buffer msg_buffer{}; + std::string key; + bool enable = false; + std::string type; + Output_Format output_format; + Frequency_Limit_Multi sbs_flm{}; + virtual void handle_in_loop() { + + } + virtual Psc::JSON to_json() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(key); + Ret_J(enable); + Ret_J(type); + ret.append({"output_format", output_format.to_Json()}); + return ret; + } + virtual void from_json(const Psc::JSON *that_json) { + Get_J(key) + Get_J(enable) + Get_J(type) + output_format.from_json(that_json->get("output_format")); + } + virtual Psc::JSON get_custom_state_json() { + return Psc::JSON::object(); + } + virtual Psc::JSON get_state_json() { + Psc::JSON ret = msg_buffer.state_json(); + ret.append_list(get_custom_state_json().children); + return ret; + } + bool check_and_open() { + if (_open_) return true; + bool ret = _open(); + _open_ = true; + return ret; + } + + void close() { + _open_ = false; + _close(); + } + + std::string to_string() { + return "Data_Feed【" + VAR_STR_3(key, enable, type) + "】"; + } +protected: + Data_Feed() = default; + bool _open_ = false; + virtual bool _open() { + return true; + } + + virtual void _close() { + + } +}; + + +class Data_Feed_TCP_Server : public Data_Feed { +public: + std::uint16_t port{}; + size_t connect_user_buffer_size{}; + size_t connect_system_buffer_size{}; + Psc::asio_socket::TCP_Server svr; + Psc::JSON get_custom_state_json() override { + auto& state = svr.state; + Psc::JSON ret = Psc::JSON::object(); + Ret_J(connect_system_buffer_size) + Ret_J(connect_user_buffer_size) + ret.key = "fixed"; + auto t = VAR_JSON_1(state); + t.append(ret); + return t; + } + + ~Data_Feed_TCP_Server() override {} + void handle_in_loop() override { + //std::cout << socket.to_string() << "flush_clients" << std::endl; + svr.flush_clients(); + svr.tick(); + } + void from_json(const Psc::JSON* that_json) override { + Data_Feed::from_json(that_json); + Get_J(port); + Get_J(connect_user_buffer_size) + Get_J(connect_system_buffer_size) + } + Psc::JSON to_json() override { + Psc::JSON ret = Data_Feed::to_json(); + Ret_J(port); + Ret_J(connect_user_buffer_size) + Ret_J(connect_system_buffer_size) + return ret; + } + void _close() override { svr.close(); } + bool _open() override { + svr.set_connect_user_buffer_size(connect_user_buffer_size); + svr.set_connect_system_buffer_size(connect_system_buffer_size); + svr.set_tcp_no_delay(false); + svr.create(); + bool ok = svr.listen("0.0.0.0", port); + return true; + } + Psc::JSON get_clients_json() { + Psc::JSON ret = Psc::JSON::array(); + for (const auto& conn : svr.get_all_clients()) { + auto& info = conn->info; + Psc::JSON cur = Psc::JSON::object(); + auto& fd = info.fd; + auto& cur_ip = info.sockaddr.ip; + auto& cur_port = info.sockaddr.port; + cur.append({"base", VAR_STR_3(cur_ip, cur_port, fd)}); + cur.append({"state", conn->send_buffer.state_str()}); + { + auto& ins = conn->push_speed.instant_speed; + auto& avr = conn->push_speed.average_speed; + cur.append({"send_speed(KB/s)", VAR_STR_2(ins, avr)}); + } + { + auto& ins = conn->lose_speed.instant_speed; + auto& avr = conn->lose_speed.average_speed; + cur.append({"lose_speed(KB/s)", VAR_STR_2(ins, avr)}); + } + { + auto& ins = conn->send_num.instant; + auto& avr =conn->send_num.average; + cur.append({"send_num(byte/次)", VAR_STR_2(ins, avr)}); + } + ret.append(cur); + } + return ret; + } +}; + + +class Data_Feed_TCP_Client : public Data_Feed { +public: + void handle_in_loop() override { + cli.tick(); + } + std::string url; + Psc::JSON get_custom_state_json() override { + auto& state = cli.state; + return VAR_JSON_1(state); + } + std::uint16_t port{}; + Psc::asio_socket::TCP_Client cli; + ~Data_Feed_TCP_Client() override = default; + void _close() override { cli.close(); } + bool _open() override { + Psc::asio_socket::Sockaddr_In sockaddr_in; + sockaddr_in.ip = url; + sockaddr_in.port = port; + cli.set_dest_address(sockaddr_in); + cli.create(); + return true; + } + void from_json(const Psc::JSON* that_json) override { + Data_Feed::from_json(that_json); + Get_J(url) + Get_J(port); + } + Psc::JSON to_json() override { + Psc::JSON ret = Data_Feed::to_json(); + Ret_J(url); + Ret_J(port); + return ret; + } +}; + + + +class Data_Feed_UDP_Server : public Data_Feed { +public: + Psc::JSON get_custom_state_json() override { + auto& state = svr.state; + return VAR_JSON_1(state); + } + + + + Data_Feed_UDP_Server()= default; + ~Data_Feed_UDP_Server() override = default; + void handle_in_loop() override; + [[nodiscard]] Psc::JSON get_clients_json() const { + Psc::JSON ret = Psc::JSON::array(); + for (const auto& i : svr.clients) { + Psc::JSON cur = Psc::JSON::object(); + cur.append({"ip", i.ip}); + cur.append({"port", i.port}); + ret.append(cur); + } + return ret; + } + + void from_json(const Psc::JSON* that_json) override { + Data_Feed::from_json(that_json); + Get_J(port); + } + Psc::JSON to_json() override { + Psc::JSON ret = Data_Feed::to_json(); + Ret_J(port); + return ret; + } + void _close() override { svr.close(); } + bool _open() override; + std::uint16_t port{}; + Psc::asio_socket::UDP_Server svr; +}; + + +class Data_Feed_UDP_Client : public Data_Feed { +public: + Data_Feed_UDP_Client() {} + ~Data_Feed_UDP_Client() override {} + Psc::JSON get_custom_state_json() override { + auto& state = cli.state; + return VAR_JSON_1(state); + } + void handle_in_loop() override { + cli.tick(); + } + Psc::asio_socket::UDP_Client cli; + std::string url; + std::uint16_t port{}; + void from_json(const Psc::JSON* that_json) override { + Data_Feed::from_json(that_json); + Get_J(url) + Get_J(port); + } + Psc::JSON to_json() override { + Psc::JSON ret = Data_Feed::to_json(); + Ret_J(url); + Ret_J(port); + return ret; + } + void _close() override { + std::cout << "udp客户端 目标地址:" << cli.dest_address.to_string() << "关闭!" << std::endl; + cli.close(); + } + bool _open() override { + cli.create(); + cli.set_dest_address(url, port); + cli.connect(); + return true; + } +}; + + + +inline std::shared_ptr create_data_feed_from_type(const std::string& t) { + std::shared_ptr ret{}; + if (t == "Data_Feed_TCP_Server") ret = std::make_shared(); else + if (t == "Data_Feed_UDP_Server") ret = std::make_shared(); else + if (t == "Data_Feed_TCP_Client") ret = std::make_shared(); else + if (t == "Data_Feed_UDP_Client") ret = std::make_shared(); else { + std::cout << "未知的 Data_Feed type类型! t:[" << t << "]"<< std::endl; + throw std::invalid_argument("unknown Data_Feed type: " + t); + } + return ret; +} + +struct Data_feed_Config { + Psc::String_Pool pool_ = Psc::String_Pool(1600, 8* 1024); // 8192 * 1600 12.5 MB + Ordered_Map> map; + std::atomic empty_wait_milliseconds{}; + std::atomic packet_byte_size{}; + size_t tcp_server_default_connect_user_buffer_size{}; + size_t tcp_server_default_connect_system_buffer_size{}; + Data_feed_Config() = default; + void init(const Psc::JSON* that_json) { + auto connect = that_json->get("list"); + for (auto& it : connect->children) { + auto type = it.get_string("type"); + std::shared_ptr t = create_data_feed_from_type(type); + t->from_json(&it); + map.push_back(t); + } + Get_J(empty_wait_milliseconds) + Get_J(tcp_server_default_connect_user_buffer_size) + Get_J(tcp_server_default_connect_system_buffer_size) + } + + Psc::JSON list() { + auto connect_json = Psc::JSON::array(); + for (auto& it : map.list()) { + connect_json.children.emplace_back(it->to_json()); + } + return connect_json; + } + + Psc::JSON to_json() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(empty_wait_milliseconds) + Ret_J(packet_byte_size) + Ret_J(tcp_server_default_connect_user_buffer_size) + Ret_J(tcp_server_default_connect_system_buffer_size) + ret.append({"list", list()}); + return ret; + } + Psc::JSON get_feed(const std::string& key); + void server(Global* g); +}; +class Data_Source; +std::optional convert_to_send_format(Data_Source* ds, const std::shared_ptr& feed, const std::shared_ptr& msg); + diff --git a/module/Local_Server/Data_Feed/data_feed_thread.cpp b/module/Local_Server/Data_Feed/data_feed_thread.cpp new file mode 100644 index 0000000..667c485 --- /dev/null +++ b/module/Local_Server/Data_Feed/data_feed_thread.cpp @@ -0,0 +1,230 @@ +#include "../server/Global.h" +#include "../server/Mode_Msg_Buffer.h" +#include +#include +long long get_current_milliseconds() { + auto now = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast(now.time_since_epoch()); + return duration.count(); +} +Frequency_Limit too_many_msg_limit; +Frequency_Limit flush_limit; + + +std::optional convert_to_send_format(Data_Source* ds, const std::shared_ptr& feed, const std::shared_ptr& msg) +{ + auto& type = msg->type; + auto fs = dynamic_cast(msg->source.get()); + if (fs) { + if (fs->play_mode == Play_Mode::analysis) { + //std::cout << fs->key << " analysis i ==" << i << std::endl; + } + } + // 出口输出的条件 + auto& output_format = feed->output_format.type; + auto& use_mode_ac = feed->output_format.use_mode_ac; + auto& use_status = feed->output_format.use_status; + auto& mode_s_output_type = feed->output_format.mode_s_output_type; + // 计算出是否输出消息 + std::optional send_msg = std::nullopt; + if (type == SSR::Msg::HULC_Status) { + if (!use_status) return std::nullopt; + auto data = static_cast(msg.get()); + if (output_format == Output_Data_Format::BIN || output_format == Output_Data_Format::BIN_ID) { + send_msg = SSR::packet_to_escape_format(data->packet); + } + } else if (type == SSR::Msg::Radarcape_status) { + auto data = static_cast(msg.get()); + if (output_format == Output_Data_Format::BIN || output_format == Output_Data_Format::BIN_ID) { + send_msg = SSR::packet_to_escape_format(data->packet); + } + } else if (type == SSR::Msg::AC) { + if (!use_mode_ac) return std::nullopt; + auto data = static_cast(msg.get()); + std::string& msg_hex = data->msg_hex; + char signal_level = data->signal_level; + SSR::MLAT_timestamp& mlat_timestamp = data->mlat_timestamp; + if (output_format == Output_Data_Format::BIN) { + //send_msg = create_Binary_Format_memory(msg_hex, signal_level, &mlat_timestamp); + send_msg = SSR::packet_to_escape_format(msg->packet); + } else if (output_format == Output_Data_Format::BIN_ID) { + // std::uint32_t id; + // { + // id = Global::instance()->mlat_server.id; + // } + // auto bin_id_packet = mode_s_msg_packet_to_server_packet(id, data->packet); + // send_msg = packet_to_escape_format(bin_id_packet); + } else if (output_format == Output_Data_Format::AVR) { + send_msg = SSR::create_AVR_format(msg_hex); + } else if (output_format == Output_Data_Format::AVR_MLAT) { + send_msg = create_MLAT_AVR_format(msg_hex, &mlat_timestamp); + } + } else if (type == SSR::Msg::S7 || type == SSR::Msg::S14) { + auto s_msg = static_cast(msg.get()); + std::string& icao = s_msg->icao; + SSR::Downlink_Format& df = s_msg->df; + std::string& msg_hex = s_msg->msg_hex; + auto& signal_level = s_msg->signal_level; + SSR::MLAT_timestamp& mlat_timestamp = s_msg->mlat_timestamp; + auto aircraft = ds->get_aircraft(s_msg->icao); + + if (output_format == Output_Data_Format::SBS) { + bool sbs_out_put = false; + if (feed->output_format.sbs_only_pos) { + auto s = ds->get_aircraft(s_msg->icao); + if (s) { + auto pos = s->pos(); + if (pos.has_value()) { + if (feed->sbs_flm.test(s_msg->icao)) { + sbs_out_put = true; + } + } + } + } else { + if (feed->sbs_flm.test(s_msg->icao)) { + sbs_out_put = true; + } + } + if (!sbs_out_put) return std::nullopt; + + send_msg = ""; + SSR::SBS_MSG msg_base; + if (!aircraft) return std::nullopt; + msg_base.set(aircraft.get()); + for (int msg_t = 1; msg_t <= 8; ++msg_t) { + send_msg->append(msg_base.to_msg(msg_t) + "\n"); + } + send_msg->append(msg_base.to_ID() + "\n"); + send_msg->append(msg_base.to_AIR() + "\n"); + send_msg->append(msg_base.to_CLK() + "\n"); + send_msg->append(msg_base.to_SEL() + "\n"); + send_msg->append(msg_base.to_STA() + "\n"); + return send_msg; + } + + bool DF_11_17_18 = df == SSR::Downlink_Format::All_Call_Reply_11 || df == SSR::Downlink_Format::Extended_Squitter_17 || df == + SSR::Downlink_Format::Extended_Squitter_Non_Transponder_18; + if (mode_s_output_type == Mode_S_Output_Type::DF_11_17_18 && !DF_11_17_18) return std::nullopt; + if (mode_s_output_type == Mode_S_Output_Type::NO_POS_Mode_S) { + if (aircraft && aircraft->pos().has_value()) { + return std::nullopt; + } + } + + if (output_format == Output_Data_Format::BIN) { + send_msg = SSR::packet_to_escape_format(s_msg->packet); + //send_msg = create_Binary_Format_memory(msg_hex, signal_level, &mlat_timestamp); + } else if (output_format == Output_Data_Format::BIN_ID) { + // std::uint32_t id; + // { + // id = Global::instance()->mlat_server.id; + // } + // auto bin_id_packet = mode_s_msg_packet_to_server_packet(id, data->packet); + // send_msg = packet_to_escape_format(bin_id_packet); + } else if (output_format == Output_Data_Format::AVR) { + send_msg = SSR::create_AVR_format(msg_hex); + } else if (output_format == Output_Data_Format::AVR_MLAT) { + send_msg = create_MLAT_AVR_format(msg_hex, &mlat_timestamp); + } else { + std::cerr << "Unknown output format " << VAR_STR_1(output_format) << std::endl; + return std::nullopt; + } + } + return send_msg; +} + +void data_feed_thread(std::atomic& running) { + auto g = Global::instance(); + for (auto& t : g->mode_acs.data_feed_config.map.list()) { + if (t->enable) { + auto ret = t->check_and_open(); + if (!ret) { + std::cout << "[Data_feed_Config] [" << t->key << "] 第一次打开失败! 程序退出! " << std::endl; + } + } + } + auto& mode_acs = Global::instance()->mode_acs; + auto& cfg = mode_acs.data_feed_config; + auto& pool = cfg.pool_; + auto t = cfg.empty_wait_milliseconds.load(); + auto& report_data_feed_msg_mum = mode_acs.report_data_feed_msg_mum; + std::vector> enable_feeds; // all_feed 保证声明周期仍然存在 + auto monitor_msg_live = mode_acs.monitor_msg_live.load(); + bool all_empty = true; + while (running.load(std::memory_order_acquire) == true) { + // 这两个每次循环都要重新计算 保证实时性 + // if (flush_limit.test()) { + auto all_feed = cfg.map.list(); + enable_feeds.clear(); + for (auto& feed : all_feed) { + if (!feed->enable) continue; + enable_feeds.push_back(feed); + // 执行特定的刷新逻辑 比如 tcp_server 检查自身的连接 + feed->handle_in_loop(); + } + + + int m = static_cast(enable_feeds.size()); + for (int j = 0; j < m; j++) { + auto& feed = enable_feeds[j]; + auto s_num = feed->msg_buffer.mode_s_msg_num.load(); + auto other_num = feed->msg_buffer.mode_other_msg_num.load(); + //const std::vector>& all = feed->msg_buffer.get_all(); + const std::vector& all = feed->msg_buffer.get_all(); + // 释放内存池 + + + Pool_Guard pg(&pool, all); + + auto num = report_data_feed_msg_mum.load(); + if (monitor_msg_live) { + if (num != 0 && all.size() > num) { + if (too_many_msg_limit.test()) { + std::ostringstream oss; + oss << "feed_key:" << feed->key << " "; + oss << "recv_s:" << s_num << " "; + oss << "recv_other:" << other_num << " "; + std::cout << oss.str() << std::endl; + } + } + } + if (all.empty()) continue; + all_empty = false; + auto n = all.size(); + + + + for (auto i = 0; i < n; i++) { + const auto& str = all[i]; + if (str->empty()) { + std::cout << "警告:未知原因:" << feed->key << "内有空的消息体" << std::endl; + continue; + } + if (feed->type == "Data_Feed_TCP_Server") { + auto it = dynamic_cast(feed.get()); + it->svr.write_to_all_clients(*str); + } + else if (feed->type == "Data_Feed_UDP_Server") { + auto it = dynamic_cast(feed.get()); + it->svr.write_to_all_clients(*str); + } + else if (feed->type == "Data_Feed_TCP_Client") { + auto it = dynamic_cast(feed.get()); + it->cli.send(*str); + } + else if (feed->type == "Data_Feed_UDP_Client") { + auto it = dynamic_cast(feed.get()); + it->cli.send(*str); + } + } + } + if (t != 0) { + if (all_empty) { + std::this_thread::sleep_for(std::chrono::milliseconds(t * 2)); + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(t)); + } + } + } +} + diff --git a/module/Local_Server/Data_Feed/export.h b/module/Local_Server/Data_Feed/export.h new file mode 100644 index 0000000..3eaa8da --- /dev/null +++ b/module/Local_Server/Data_Feed/export.h @@ -0,0 +1,2 @@ +#pragma once +#include "Data_Feed.h" diff --git a/module/Local_Server/Data_Feed/global.h b/module/Local_Server/Data_Feed/global.h new file mode 100644 index 0000000..a5ea2c8 --- /dev/null +++ b/module/Local_Server/Data_Feed/global.h @@ -0,0 +1,6 @@ +#pragma once +#include "../server/Mode_Msg_Buffer.h" +#include "Core/Serial/Serial.h" +#include "Core/Statistics/Frequency_Limit.h" +#include "Core/socket/TCP_Server.h" +#include "Core/socket/export.h" diff --git a/module/Local_Server/Data_Source/BaseStation.cpp b/module/Local_Server/Data_Source/BaseStation.cpp new file mode 100644 index 0000000..f25df77 --- /dev/null +++ b/module/Local_Server/Data_Source/BaseStation.cpp @@ -0,0 +1,43 @@ +#include "BaseStation.h" + + + +namespace SSR { +struct HULC_Status_Message; +} + + +std::optional BaseStation::get_pos() { + std::lock_guard g(mtx); + return SSR::CPR::Position{hulc.get_latitude(), hulc.get_longitude()}; +} +void BaseStation::set_msg(const SSR::HULC_Status_Message& msg) { + std::lock_guard g(mtx); + hulc = msg; + if (handle_when_updated) handle_when_updated(msg); +} +std::string BaseStation::to_string() { + std::lock_guard g(mtx); + return "BaseStation:[" + std::to_string(hulc.get_latitude()) + ", " + std::to_string(hulc.get_longitude()) + "]"; +} +Psc::JSON BaseStation::to_Json() { + std::lock_guard g(mtx); + auto ret = Psc::JSON::object(); + ret.append({"序列号", hulc.SerNum}); + ret.append({"状态标志", hulc.Flags}); + ret.append({"内部使用", hulc.I_U_}); + ret.append({"时间", Psc::utc_2_local_time(hulc.xTime)}); + ret.append({"latitude", hulc.get_latitude()}); + ret.append({"longitude", hulc.get_longitude()}); + ret.append({"height", hulc.Alt}); + ret.append({"卫星数量", hulc.Sat}); + ret.append({"HDOP", hulc.get_HDOP()}); + ret.append({"GPS设备检测到", hulc.GPS_device_detected()}); + ret.append({"GPS有效", hulc.GPS_valid()}); + ret.append({"GPS有效定位", hulc.GPS_has_valid_fix()}); + ret.append({"GPS高精度时间", hulc.GPS_high_accuracy_time()}); + ret.append({"启动以来TX队列溢出", hulc.TX_queue_overflow_since_startup()}); + ret.append({"过去一秒TX队列溢出", hulc.TX_queue_overflow_last_second()}); + ret.append({"发现过多NMEA数据", hulc.excessive_NMEA_found()}); + return ret; +} \ No newline at end of file diff --git a/module/Local_Server/Data_Source/BaseStation.h b/module/Local_Server/Data_Source/BaseStation.h new file mode 100644 index 0000000..432f649 --- /dev/null +++ b/module/Local_Server/Data_Source/BaseStation.h @@ -0,0 +1,20 @@ +#pragma once +#include "global.h" + + + +class BaseStation { +public: + std::string to_string(); + Psc::JSON to_Json(); + std::optional get_pos(); + void set_msg(const SSR::HULC_Status_Message& msg); + std::function handle_when_updated = nullptr; +protected: + std::mutex mtx; + SSR::HULC_Status_Message hulc{}; +}; +class Global; + + + diff --git a/module/Local_Server/Data_Source/Data_Source.cpp b/module/Local_Server/Data_Source/Data_Source.cpp new file mode 100644 index 0000000..525c32c --- /dev/null +++ b/module/Local_Server/Data_Source/Data_Source.cpp @@ -0,0 +1,1078 @@ +#include "Data_Source.h" + +#include + +#include "../server/Global.h" +#include "Database.h" + +Psc::serial::Serial* create_serial(const std::string& serial_name, Baud_Rate_Type baud_rate) { + auto serial = new Psc::serial::Serial; + serial->set_serial_name(serial_name); + serial->set_baud_rate(baud_rate); + serial->set_parity(Psc::serial::Parity::NoParity); + serial->set_data_bits(Psc::serial::DataBits::Data8); + serial->set_stop_bits(Psc::serial::StopBits::OneStop); + serial->set_flow_control(Psc::serial::FlowControl::HardwareControl); + serial->set_buffer_byte_size(10 * 1024); + if (!serial->open()) + { + std::cerr << "createSerial " + serial_name + ":" + std::to_string(baud_rate) + " 打开串口失败!\n"; + Psc::fail_fast(); + return nullptr; + } else + { + // std::cerr << "createSerial " + serial_name + ":" + std::to_string(baud_rate) + " 打开串口成功!\n"; + } + return serial; +} + +std::shared_ptr create_from_json(const JSON* that_json) { + std::string type = that_json->get_string("type"); + std::shared_ptr ret = create_data_source_from_type(type); + ret->from_json(that_json); + // if (ret->enable) + // { + // ret->open(); + // } + return ret; +} + + +std::shared_ptr Data_Source::that() { + return shared_from_this(); +} +Psc::JSON Data_Source::get_state() { + Psc::JSON ret = Psc::JSON::object(); + ret.append_list(get_custom_state_json().children); + ret.append({"ds: read_speed(byte)", read_speed}); + ret.append({"ds: value_statistics(byte)", value_statistics}); + return ret; +} + +std::string Data_Source::thread_key() const { + //return "Data_Source_Handle_Thread:[" + key + "]"; + return key + "_DS_HT"; +} + +void Data_Source::test_and_attach_thread() { + //static int t = Global::instance()->mode_acs.read_milliseconds; + Global::instance()->thread_manager.test_and_start_thread(thread_key(), [this](std::atomic& running) { + if (enable) { + open(); + } + while (running.load(std::memory_order_acquire) == true) + { + handle_in_loop(); + handle_mode_acs_source(); + } + }); +} + + +std::shared_ptr Data_Source::create_msg(const std::shared_ptr &source, + const std::string &packet) { + if (packet[0] != 0x1a) + { + std::cout << "first:" << mem2hex(packet) << std::endl; + return nullptr; + } + std::string error_len = key + " " + LOG_POS_SIMPLE + std::string(" ") + "mode_s_error_length"; + if (packet.size() < 2) return nullptr; + auto mt = packet[1]; + if (mt == SSR::Msg::AC) + { + if (packet.size() != SSR::Msg::AC_len) + { + SSR::mode_s_logger->c_debug(error_len, {}, + std::to_string(mt) + " size:" + std::to_string(packet.size()) + + " hex:" + mem2hex(packet) + " should:" + std::to_string(SSR::Msg::AC_len)); + mode_ac_statistic.add_length_error(); + return nullptr; + } + mode_ac_statistic.add(); + return std::make_shared(that(), packet); + } + if (mt == SSR::Msg::S7) + { + if (packet.size() != SSR::Mode_S_Msg::S7_len) + { + SSR::mode_s_logger->c_debug(error_len, {}, + std::to_string(mt) + " size:" + std::to_string(packet.size()) + + " hex:" + mem2hex(packet) + " should:" + std::to_string(SSR::Msg::S7_len)); + mode_s_statistic.add_length_error("[length error] Msg::S7 handle_mode_s_source"); + return nullptr; + } + return std::make_shared(that(), packet); + } + + if (mt == SSR::Msg::S14) + { + if (packet.size() != SSR::Msg::S14_len) + { + SSR::mode_s_logger->c_debug(error_len, {}, + std::to_string(mt) + " size:" + std::to_string(packet.size()) + + " hex:" + mem2hex(packet) + " should:" + std::to_string(SSR::Msg::S14_len)); + mode_s_statistic.add_length_error("[length error] Msg::S14 handle_mode_s_source"); + return nullptr; + } + return std::make_shared(that(), packet); + } + + if (mt == SSR::Msg::Radarcape_status) { + return nullptr; // 不知道如何解析跳过 + if (packet.size() != SSR::Msg::Radarcape_status_len) + { + SSR::mode_s_logger->c_debug(error_len, {}, + std::to_string(mt) + " size:" + std::to_string(packet.size()) + + " hex:" + mem2hex(packet) + " should:" + std::to_string(SSR::Msg::Radarcape_status_len)); + mode_s_statistic.add_length_error("[length error] Msg::Radarcape_status_len handle_mode_s_source"); + return nullptr; + } + return std::make_shared(that(), packet); + } + + if (mt == SSR::Msg::HULC_Status) + { + if (packet.size() != SSR::Msg::HULC_len) + { + + // 找不到协议 size:23 hex:1A34195F0000001500B502FF06F423CE50000090000000 should:5 + SSR::mode_s_logger->c_debug(error_len, {}, + Psc::to_string(type) + " HULC size:" + + std::to_string(packet.size()) + " hex:" + mem2hex(packet)); + return nullptr; + } + return std::make_shared(that(), packet); + } + // std::cout << "unknown_type:" << memory2hex(packet); + rotating_log("unknown_type", mem2hex(packet)); + return nullptr; +} + + +void Data_Source::update_msg_day_time(std::shared_ptr msg) { + auto cur = std::dynamic_pointer_cast(msg); + if (cur != nullptr) { + auto& pre = last_prase_msg; + if (!pre) { + pre = cur; + return; + } + cur->day_num = pre->day_num; + if (cur->time() < pre->time()) { + ++cur->day_num; + } + } +} + +void Data_Source::handle_mode_acs_source() { + std::string mode_data = read(); + + if (mode_data.empty()) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + //std::cout << key + "数据为空,没有消息积压" << std::endl; + return; + } + + + auto size = mode_data.size(); + read_speed.update(size); + value_statistics.update(size); + + pre_handle(mode_data); + + SSR::Binary_Format_handle_buffer(buffer, mode_data, [this](std::string& packet) { + auto msg = create_msg(that(), packet); + if (!msg) return; + update_msg_day_time(msg); + push_to_feed(msg); + auto mt = msg->type; + bool mode_s = mt == SSR::Msg::S7 || mt == SSR::Msg::S14; + if (mt == SSR::Msg::HULC_Status) { + handle_HULC(packet); + } + else if (mt == SSR::Msg::Radarcape_status) { + auto radarcape_msg = SSR::create_Radarcape_STATUS_Message(packet); + std::cout << radarcape_msg.toJson().to_json_string() << std::endl; + SSR::mode_s_logger->debug("Radarcape_status/radarcape", {}, radarcape_msg.toJson().to_json_string()); + } + else if (mode_s) + { + // 拓展点 + handle_mode_s(std::dynamic_pointer_cast(msg)); + } + }); +} + + +void Data_Source::test_and_stop_thread() const { + Global::instance()->thread_manager.test_and_stop_thread(thread_key()); +} + +Psc::JSON Data_Source::get_all_connect_feed_status() { + Psc::JSON ret = Psc::JSON::object(); + std::vector list; + { + std::lock_guard g(cdf_mtx); + list = cached_data_feed_key_list; + } + for (auto& i : list) { + Psc::JSON cur = Psc::JSON::object(); + auto odf = Global::instance()->mode_acs.data_feed_config.map.get(i.key); + if (odf.has_value()) { + const auto& df = odf.value(); + i.mode_s_cache_num.update(df->msg_buffer.mode_s_msg_num); + i.mode_other_cache_num.update(df->msg_buffer.mode_other_msg_num); + cur.append({"mode_s_msg_num", i.mode_s_cache_num.to_json()}); + cur.append({"mode_other_msg_num", i.mode_other_cache_num.to_json()}); + } + ret.append({i.key, cur}); + } + return ret; +} +Psc::JSON Data_Source::get_all_connect_feed() { + Psc::JSON ret = Psc::JSON::array(); + std::vector list; + { + std::lock_guard g(cdf_mtx); + list = cached_data_feed_key_list; + } + for (auto& i : list) { + Psc::JSON cur = Psc::JSON::object(); + cur.append({"key", i.key}); + ret.append(cur); + } + return ret; +} + + +std::vector readLines(const std::string& path) { + std::vector lines; + + std::filesystem::path p = std::filesystem::path((const char8_t*)path.c_str()); + + //std::filesystem::path p = std::filesystem::u8path(path); + std::ifstream istream(p); + if (!istream) { + std::cerr << "读取模拟数据源 readLines 无法打开文件:" << path << std::endl; + Psc::fail_fast(); + } + + // 将整个文件读入一个字符串 + std::stringstream stream; + stream << istream.rdbuf(); + // 使用 stringstream 按行分割内容 + std::string line; + char ch; + while (stream.get(ch)) { + if (ch == ' ') { + continue; + } + if (ch == '\r' || ch == '\n') { + if (!line.empty()) { + lines.push_back(line); + line.clear(); + } + + // if (ch == '\r') { + // stream.get(); // 吃掉 \n + // } + // if (ch == '\n') { + // stream.get(); // 吃掉 \n + // } + // 处理 \r\n 组合:如果当前是 \r,下一个是 \n,跳过它 + // if (ch == '\r' && stream.peek() == '\n') { + // stream.get(); // 吃掉 \n + // } + + } else { + line += ch; + } + if (line.size() == 1000) { + lines.push_back(line); + line.clear(); + } + } + // 最后一行如果没有换行符也处理一下 + if (!line.empty()) { + lines.push_back(line); + } + std::ostringstream oss; + oss << path + " 总行数:" + std::to_string(lines.size()) + " 有效行数:" + std::to_string(lines.size()) + "\n" << std::flush; + std::cout << oss.str() << std::flush; + return lines; +} + + + +std::string extractID(const std::string& logLine) { + size_t lastSpacePos = logLine.rfind(" "); // 查找最后一个空格 + if (lastSpacePos != std::string::npos) { + return logLine.substr(lastSpacePos + 1); // 从最后一个空格之后提取字符串 + } + return logLine; +} + +time_t convert_to_timestamp(const std::string& str) { + // 创建一个结构体 tm 来存储解析后的时间 + std::tm timeStruct = {}; + + std::istringstream ss(str); + ss >> std::get_time(&timeStruct, "%Y-%m-%d %H:%M:%S"); + if (ss.fail()) { + std::cerr << "Failed to parse time" << std::endl; + return -1; // 如果解析失败,返回 -1 + } + + // 将 tm 转换为 time_t(时间戳) + time_t timestamp = std::mktime(&timeStruct); + + if (timestamp == -1) { + std::cerr << "Failed to convert to time_t" << std::endl; + return -1; // 如果转换失败,返回 -1 + } + return timestamp; +} + +std::tuple extractID2(const std::string& logLine) { + size_t lastSpacePos = logLine.rfind(" "); // 查找最后一个空格 + time_t t = convert_to_timestamp(logLine.substr(0, 19)); + if (lastSpacePos != std::string::npos) { + return {logLine.substr(lastSpacePos + 1), t}; // 从最后一个空格之后提取字符串 + } + return {logLine, t}; +} + + +std::string bin_format(const std::string& hex, time_t t){ + std::tm* currentTime = std::localtime(&t); + auto sec = currentTime->tm_hour * 3600 + currentTime->tm_min * 60 + currentTime->tm_sec; + SSR::MLAT_timestamp a(sec, 0); + return create_Binary_Format_memory(hex, 0, &a); +} + +std::optional File_Data_Source::get_raw_line(int& ret_index) { + if (index == 0 && part_infos.empty()) + { + ret_index = -1; + return std::nullopt; + } + std::optional log_info = std::nullopt; + if (index == part_infos.size()) + { + if (play_mode == Play_Mode::loop) + { + index = 0; + } else + { + if (!have_report_play_back_all_success) + { + std::ostringstream oss; + oss << SSR::get_current_date() << " " << SSR::get_current_time() << " [进程:" << std::this_thread::get_id() << "] " << file_path + " 全部加载成功!\n" << std::flush; + std::cout << oss.str() << std::flush; + have_report_play_back_all_success = true; + } + ret_index = -1; + return std::nullopt; + } + } + ret_index = index + 1; + return part_infos[index++]; +} + + +// 1A 33 1A 1A F1 FB 87 73 7E 7F a8001d81a87543b0a80000 + +std::string get_true_from_raw_line(Data_Source* ds, File_Data_Type data_type, int index, const std::string& log_info) { + std::string ret; + + // 2025-01-06 07:20:17 [info] dc2541e65001401a3119b2dd1c7af67132201 + if (data_type == File_Data_Type::BIN) { + return log_info; + } + else if (data_type == File_Data_Type::BIN_Text) + { + auto info = extractID(log_info); + ret = hex2mem(info); + } + else if (data_type == File_Data_Type::AVR) + { + auto [info, time] = extractID2(log_info); + ret = bin_format(info, time); + } + // 1A 33 10 01 A5 31 FB 9A 5D 8D 78 0E 47 99 08 8E 32 B0 08 BE E9 8A E6 1A 32 10 01 AA 14 61 7E 5E 02 E6 0F 38 7D 87 08 + // 1A 32 10 01 AD B5 64 F7 63 5D 78 0F 9D BA 93 BA + else if (data_type == File_Data_Type::BIN_Blank_Text) + { + std::string info = log_info; + std::string result = ds->last_char; + ds->last_char = ""; + + + + for (char c : info) + { + if (std::isxdigit(c)) { // 会判断字符 c 是否是十六进制数字字符 + result += c; + } else if (std::isspace(c)) { + // + } else { + std::ostringstream oss; + oss << "存在非法字符[" << c << "]: value:" << (int)c << " in:" << log_info << LOG_POS; + server_logger->c_debug("非法字符:", {}, oss.str()); + return ""; + } + } + auto size = result.size(); + if (size % 2 != 0) { + // 保存最后一个字符 + ds->last_char = result.empty() ? "" : std::string(1, result.back()); + result.pop_back(); + } + ret = hex2mem(result); + + // if (size % 2 != 0) + // { + // std::ostringstream oss; + // size_t prefix_num = 20; + // size_t suffix_num = 20; + // auto origin_size = log_info.size(); + // prefix_num = std::min(origin_size, prefix_num); + // suffix_num = std::min(origin_size, suffix_num); + // std::string prefix_data = log_info.substr(0, prefix_num); + // std::string suffix_data = log_info.substr(log_info.size() - suffix_num, suffix_num); + // oss << "index:" << index << " 长度不为偶数 不能转换成内存:" << "[内容]" << "result_size" << result.size() << " origin_size:[" << origin_size << "]" << std::endl << + // " prefix[" << prefix_num << "]:" << prefix_data << std::endl << + // " suffix[" << suffix_num << "]:" << suffix_data << std::endl << + // std::endl; + // server_logger->c_debug("长度不为偶数", {}, oss.str()); + // //std::cout << "result:" << log_info << std::endl << std::endl; + // return ""; + // } + + } + else if (data_type == File_Data_Type::BIN_Blank_One_Line_With_Escape) { + auto size = log_info.size(); + if (size % 2 != 0) + { + return hex2mem(log_info.substr(0, size - 1)); + } + return hex2mem(log_info); + } + else if (data_type == File_Data_Type::BIN_Blank_One_Line_No_Escape) { + auto size = log_info.size(); + std::string data; + if (size % 2 != 0) + { + data = log_info.substr(0, size - 1); + } else { + data = log_info; + } + return SSR::packet_to_escape_format(hex2mem(data)); + } + else if (data_type == File_Data_Type::SIMPLE_BIN_Blank) + { + std::string info = log_info; + std::string result; + for (char c : info) + { + if (c != ' ') + { + result += c; + } + } + // 1 + 1 + 6 + 1 + 2/7/14 + // 1a + type time_stamp signal_level data + int size = result.size(); + if (size != 2*2 && size != 7*2 && size != 14*2) + { + std::cout << "line_offset:[" << index << "] " << "大小" << size << " 不为偶数,或不为2,7,14 不能转换成二进制格式:" << result << std::endl; + return ""; + } + std::string head("\x1a"); + std::string type; + std::string time_stamp = "\x11\x22\x33\x44\x55\x66"; + std::string signal_level("\xFF"); + if (size == 2 * 2) + { + type = R"(1)"; + } else if (size == 7 * 2) + { + type = R"(2)"; + } else if (size == 14 * 2) + { + type = R"(3)"; + } + ret = SSR::packet_to_escape_format(head + type + time_stamp + signal_level + hex2mem(result)); + + //std::cout << ret.size() << std::endl; + } + else + { + std::cout << "未知的回放类型 " << static_cast(data_type) << std::endl; + Psc::fail_fast(); + } + return ret; +} + + + + +void File_Data_Source::_close() { + std::lock_guard g(mtx); + index = 0; + part_infos.clear(); +} + +bool File_Data_Source::_open() { + std::lock_guard g(mtx); + auto path = get_true_file_path(); + + namespace fs = std::filesystem; + if (!fs::exists(path)) { + state = "文件不存在"; + return false; + } + + if (data_type == File_Data_Type::BIN) { + part_infos = readBinaryFileAsString(path, 1000); + } + else { + part_infos = readLines(path); + } + state = "已加载" + to_string(part_infos.size()) + "长度数据!"; + return true; +} + + +std::vector File_Data_Source::readBinaryFileAsString(const std::string& filepath, size_t part_size) { + // 打开文件(以二进制模式) + std::ifstream file(filepath, std::ios::binary); + + if (!file) { + std::cerr << "无法打开文件: " << filepath << std::endl; + return {}; // 文件打开失败,返回空vector + } + + // 获取文件大小 + file.seekg(0, std::ios::end); + size_t fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + // 存储读取的部分 + std::vector parts; + + // 读取文件的每一部分 + size_t bytesRead = 0; + while (bytesRead < fileSize) { + // 计算每个部分的长度(最后一部分可能小于part_size) + size_t remaining = fileSize - bytesRead; + size_t currentPartSize = (remaining < part_size) ? remaining : part_size; + + // 创建buffer来存储当前部分 + std::string buffer(currentPartSize, '\0'); + + // 读取当前部分 + file.read(&buffer[0], currentPartSize); + + // 将读取的部分添加到vector + parts.push_back(std::move(buffer)); + + // 更新已读取字节数 + bytesRead += currentPartSize; + } + + // 关闭文件 + file.close(); + + return parts; +} + + +std::string File_Data_Source::read() { + std::lock_guard g(mtx); + int ret_index = 0; + if (play_mode != Play_Mode::analysis) + { + std::optional log_info = get_raw_line(ret_index); + return log_info.has_value() ? get_true_from_raw_line(this, data_type, ret_index, log_info.value()) : ""; + } else + { + int num = 0; + std::string ret; + std::optional log_info = get_raw_line(ret_index); + while (log_info.has_value()) + { + num++; + auto data = log_info.value(); + auto cur = get_true_from_raw_line(this, data_type, ret_index, data); + ret += cur; + // 一定要在这个位置,否则会导致遗漏报文 + if (num == 1000) { + break; + } + log_info = get_raw_line(ret_index); + } + // std::cout << "size == " << t.size() << std::endl; + return ret; + } +} + + +void File_Data_Source::handle_mode_acs_source() { + std::string mode_data = read(); + if (mode_data.empty()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + return; + } + pre_handle(mode_data); + + SSR::Binary_Format_handle_buffer(buffer, mode_data, [this](std::string& packet) { + auto msg = create_msg(that(), packet); + if (msg == nullptr) return; + update_msg_day_time(msg); + push_to_feed(msg); + auto mt = msg->type; + bool mode_s = mt == SSR::Msg::S7 || mt == SSR::Msg::S14; + // 文件回放只处理mode_s + if (mode_s) + { + // 拓展点 + cache_list.push_back(std::make_shared(std::dynamic_pointer_cast(msg))); + } + }); + + + if (cache_list.empty()) return; + // 初始化流程 + if (pre_cache == nullptr) { + pre_cache = cache_list.front(); + cache_list.pop_front(); + + // 初始化当前开始时间点 + SSR::Play_Back_Time_Point& start = player_clock.start_time; + start.day_num = pre_cache->day_num; + start.day_sec = pre_cache->msg->mlat_timestamp.daysec; + } + + + auto sys_time = player_clock.get_cur_time_point(); + while (true) { + if (cache_list.empty()) break; + auto pre = pre_cache; + auto cur = cache_list.front(); + cur->day_num = pre->day_num; + if (cur->time() < pre->time()) { + cur->day_num++; + } + cache_list.pop_front(); + pre_cache = cur; + // 时间到了 回放这个文件 + handle_mode_s(cur->msg); + if (cur->time() > sys_time) { + break; + } + } +} + +std::string Dll_Data_Source::read() { + + auto start = std::chrono::steady_clock::now(); + + if (read_func_ptr == nullptr) return ""; + + auto buf = reinterpret_cast(buffer.data()); + auto len = read_func_ptr(buf, buffer_size); + vs.update(len); + + std::string data(buf, len); + + if (data.empty()) { + auto end = std::chrono::steady_clock::now(); + auto cost = std::chrono::duration_cast(end - start).count(); + //std::cout << "Dll_Data_Source::read cost: " << cost << " us\n"; + return ""; + } + + auto ret = get_true_from_raw_line(this, data_type, -1, data); + + auto end = std::chrono::steady_clock::now(); + auto cost = std::chrono::duration_cast(end - start).count(); + + // std::cout << "Dll_Data_Source::read cost: " << cost << " us\n"; + + return ret; +} + + +// std::string Dll_Data_Source::read() { +// if (read_func_ptr == nullptr) return ""; +// auto buf = reinterpret_cast(buffer.data()); +// auto len = read_func_ptr(buf, buffer_size); +// vs.update(len); +// std::string data(buf, len); +// // std::ostringstream oss; +// // oss << "Dll_Data_Source::read == (" << data.size() << ")\n"; +// // std::cout << oss.str() << std::flush; +// if (data.empty()) return ""; +// auto ret = get_true_from_raw_line(this, data_type, -1, data); +// return ret; +// } +bool Shared_Memory_Data_Source::_open() { + sm = std::make_unique(); + sm->init(shared_memory_name, shared_memory_size); + return true; +} +void Shared_Memory_Data_Source::_close() { + sm.reset(); +} + + +std::string Shared_Memory_Data_Source::read() { + auto size = sm->shm.size(); + size_t length = size; + std::string data; + data.resize(size); + + bool ok = sm->read((uint8_t *)data.data(), length); + if (!ok) { + return ""; + } + + if (length != size) { + data.resize(length); + } + if (Global::instance()->console_config.mode_s_console) { + std::cout << "read:" << data << std::endl; + } + auto ret = get_true_from_raw_line(this, data_type, -1, data); + return ret; +} + +void Data_Source_Config::server(Global* g) { + auto& svr = g->svr; + auto& api = g->api; + std::string name = "data_source"; + // 返回所有 JSON 数据的 API + svr.Post(api + "get_data_source_connect", [this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(key, params.try_get_string("key")) + auto t = map.get(key); + JSON ret{nullptr}; + if (t.has_value()) { + ret = t.value()->get_all_connect_feed(); + } + res->setBody(warp(ret).to_json_string()); + }); + + + + svr.Post(api + "get_data_source_state", [this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(key, params.try_get_string("key")) + auto t = map.get(key); + JSON ret{nullptr}; + if (t.has_value()) { + ret = t.value()->get_state(); + } + res->setBody(warp(ret).to_json_string()); + }); + + + svr.Post(api + "get_mode_s_data_source_config", [this](HTTP_Param) { + res->setBody(warp(to_json()).to_json_string()); + }); + + svr.Post(api + svr.update + name, [this, g](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(key, params.try_get_string("key")) + HTTP_REQUIRE_VALUE(ds, map.get(key)) + // 直接关闭 + ds->close(); + ds->from_json(¶ms); + if (ds->enable) { + ds->open(); + ds->test_and_attach_thread(); + } else + { + ds->test_and_stop_thread(); + } + g->save(); + g->mode_acs.source_feed_relation_config.set_need_refresh(); + }); + svr.Post(api + svr.insert + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_PTR(data, params.get("data")) + HTTP_REQUIRE_VALUE(t, data->try_get_string("type")) + std::cout << params.to_json_string() << std::endl; + std::shared_ptr ds = create_data_source_from_type(t); + ds->from_json(data); + HTTP_REQUIRE_VALUE(enable, data->try_get_bool("enable")) + if (enable) + { + ds->open(); + ds->test_and_attach_thread(); + } + + HTTP_REQUIRE_TRUE(map.insert(index, ds), "index") + //std::cout << params.to_json_string() << std::endl; + g->mode_acs.source_feed_relation_config.set_need_refresh(); + Config::save(); + res->setBody(warp(to_json()).to_json_string()); + }); + + svr.Post(api + svr.remove + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_VALUE(ds, map.try_get(index)) + ds->close(); + ds->test_and_stop_thread(); + + HTTP_REQUIRE_TRUE(map.remove(index), "index") + g->save(); + res->setBody(warp(to_json()).to_json_string()); + g->mode_acs.source_feed_relation_config.set_need_refresh(); + }); + + svr.Post(api + svr.rise + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_TRUE(map.swap(index, index - 1), "index") + g->save(); + res->setBody(warp(to_json()).to_json_string()); + g->mode_acs.source_feed_relation_config.set_need_refresh(); + }); + svr.Post(api + svr.fall + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_TRUE(map.swap(index, index + 1), "index") + g->save(); + res->setBody(warp(to_json()).to_json_string()); + g->mode_acs.source_feed_relation_config.set_need_refresh(); + }); +} +void Data_Source::refresh_data_feed_key_list() { + //std::cout << key << " refresh_data_feed_key_list" << std::endl; + std::vector tmp; + for (auto& relation : Global::instance()->mode_acs.source_feed_relation_config.map.list()) { + if (!relation->enable) continue; + if (relation->type == "One_to_One_Relation") { + auto t = dynamic_cast(relation.get()); + + //std::cout << VAR_STR_2(t->source_key, this->key) << " refresh_data_feed_key_list" << std::endl; + if (t->source_key == this->key) { + tmp.push_back(t->feed_key); + } + } else if (relation->type == "First_Source_To_All_Feed_Relation") { + auto t = dynamic_cast(relation.get()); + std::shared_ptr first = nullptr; + for (const auto& ds : Global::instance()->mode_acs.data_source_config.map.list()) { + if (ds->enable) { + first = ds; + break; + } + } + if (first->key == key) { + for (const auto& df : Global::instance()->mode_acs.data_feed_config.map.list()) { + if (df->enable) { + tmp.push_back(df->key); + } + } + } + } + + std::sort(tmp.begin(), tmp.end()); + // 去重 + auto last = std::unique(tmp.begin(), tmp.end()); + tmp.erase(last, tmp.end()); + + + + { + std::vector tmp_info; + + // 创建 tmp_info 向量,将 tmp 的内容转化为 Cached_Source_Info 对象 + for (const auto& key : tmp) { + tmp_info.push_back({key}); + } + + { + // 进入临界区,锁住 mutex,确保线程安全 + std::lock_guard lock(cdf_mtx); + + // 1. 删除 tmp 中没有的 cached_data_feed_key_list 元素 + auto it = cached_data_feed_key_list.begin(); + while (it != cached_data_feed_key_list.end()) { + if (std::find_if(tmp_info.begin(), tmp_info.end(), + [&](const Cached_Source_Info& info) { + return info.key == it->key; + }) == tmp_info.end()) { + // 如果当前元素在 tmp 中找不到,删除它 + it = cached_data_feed_key_list.erase(it); + } else { + ++it; + } + } + + // 2. 创建 tmp 中有但 cached_data_feed_key_list 没有的元素 + for (const auto& tmp_item : tmp_info) { + auto found = std::find_if(cached_data_feed_key_list.begin(), cached_data_feed_key_list.end(), + [&](const Cached_Source_Info& cached_item) { + return cached_item.key == tmp_item.key; + }); + if (found == cached_data_feed_key_list.end()) { + // 如果 tmp_item 不在 cached_data_feed_key_list 中,添加它 + cached_data_feed_key_list.push_back(tmp_item); + } + } + } + } + } +} + +void Data_Source::push_to_feed(const std::shared_ptr& msg) { + if (need_refresh_data_feed_key_list) { + refresh_data_feed_key_list(); + need_refresh_data_feed_key_list = false; + } + auto& other_size = Global::instance()->mode_acs.mode_other_max_num; + auto& s_size = Global::instance()->mode_acs.mode_other_max_num; + std::vector list; + + + + for (const auto& item : cached_data_feed_key_list) { + auto& key = item.key; + auto opt_feed = Global::instance()->mode_acs.data_feed_config.map.get(key); + if (!opt_feed.has_value()) { + std::cout << "wrong data feed: " << key << std::endl; + break; + } + std::shared_ptr& feed = opt_feed.value(); + if (!feed->enable) continue; + + + if (msg->type == SSR::Mode_Msg::T::S7 || msg->type == SSR::Mode_Msg::T::S14) { + if (feed->msg_buffer.mode_s_msg_num > s_size) { + continue; + } else { + ++feed->msg_buffer.mode_s_msg_num; + } + } else { + if (feed->msg_buffer.mode_other_msg_num > other_size) { + continue; + } else { + ++feed->msg_buffer.mode_other_msg_num; + } + } + std::optional binary = convert_to_send_format(this, feed, msg); + if (binary.has_value()) { + feed->msg_buffer.push(binary.value()); + } + } +} + +void Data_Source::handle_mode_s(std::shared_ptr mode_s_msg) { + std::string t = mode_s_msg->mlat_timestamp.to_memory() + mode_s_msg->signal_level + Psc::hex2mem(mode_s_msg->msg_hex); + auto p = mode_s_msg->packet.substr(2); + if (t != p) { + std::cout << mem2hex(t, true, " ") << std::endl; + std::cout << mem2hex(mode_s_msg->packet, true, " ") << std::endl; + std::cout << VAR_STR_2(t, mode_s_msg->packet) << std::endl; + } + push_to_feed(mode_s_msg); + auto& cfg = Global::instance()->mode_acs; + SSR::parse_mode_s_bin(this, mode_s_msg, base_station.get_pos(),cfg.max_speed_m_s, cfg.air_pos_timeout, cfg.surface_pos_timeout); + + + + + + + auto base = get_aircraft(mode_s_msg->icao); + + if (base) + { + auto derived = std::dynamic_pointer_cast(base); + derived->refresh_external_database_info(); + } + + + + + + if (Global::instance()->mlat.merge) { + auto& mh = Global::instance()->mlat_handler; + mh.push(mode_s_msg); + auto tt = mh.get_all(); + if (!tt.empty()) { + pure_log("@/logs/mlat.log", "receive[" + std::to_string(tt.size()) + "]:\n"); + for (Mlat_MSG& t : tt) { + std::ostringstream oss; + oss << std::fixed << std::setprecision(10); + oss << "===================================================\n"; + int n = static_cast(t.size()); + auto icao = t.icao().value_or("null"); + oss << "\tmlat_msg:" << n; + oss << "\ticao:" << icao; + oss << "\thex:" << t.msg_hex() << std::endl; + + bool have_pos = false; + if (icao != "null") { + auto air = get_aircraft(icao); + if (air != nullptr) { + auto callsign = air->bds20_call_sign(); + oss << "\tcall_sign:" << callsign.value_or("null") << std::endl; + auto o_pos = air->pos(); + auto o_alt = air->altitude_meter(); + if (o_pos.has_value() && o_alt.has_value()) { + have_pos = true; + auto pos = o_pos.value(); + alt = o_alt.value(); + double x, y, z; + SSR::CPR::WGS84_LBH_to_XYZ(pos.lon, pos.lat, alt, x, y, z); + oss << "\tpos:[" << pos.lon << "," << pos.lat << "," << alt << "]" << std::endl; + oss << "\tXYZ:[" << x << "," << y << "," << z << "]" << std::endl; + } + } + } + for (int i = 0; i < t.size(); i++) { + auto& msg = t.list[i]; + oss << "\t\tds:" << msg->source->get_key(); + oss << "\ttime:" << msg->mlat_timestamp.to_string() << std::endl; + } + // if (have_pos) { + //std::cout << oss.str() << std::endl; + pure_log("@/logs/mlat.log", oss.str()); + // } + // if (t.size() > 3 && have_pos && icao == "78139F") { + // size_t i = 1; + // auto range = hex_mlat_map.equal_range(t.msg_hex()); + // // 遍历与key_to_find对应的所有元素 + // for (auto it = range.first; it != range.second; ++it) { + // oss << "\t" << i++ << " " << it->second.msg << std::endl; + // } + // oss << "===================================================\n"; + // std::cout << oss.str() << std::endl; + // pure_log("@/logs/icao/" + icao + "_mlat.log", oss.str(), true); + // } + } + } + } + +} +void Data_Source::pre_handle(const std::string& mode_data) { + static bool mode_s_console = Global::instance()->console_config.mode_s_console; + static bool record_playback = Global::instance()->console_config.record_playback; + //std::cout << source->key + " read " << mode_s_data.size() << std::endl; + if (mode_data.empty()) return; + if (record_playback) { + pure_log(get_exe_dir() + "/playback/" + key + "_playback.dat", mem2hex(mode_data, true, " ") + "\n"); + } + if (mode_s_console) { + std::cout << key + " read:[mode_s_serial]:" << mem2hex(mode_data) << std::endl; + } +} + diff --git a/module/Local_Server/Data_Source/Data_Source.h b/module/Local_Server/Data_Source/Data_Source.h new file mode 100644 index 0000000..dc8439c --- /dev/null +++ b/module/Local_Server/Data_Source/Data_Source.h @@ -0,0 +1,527 @@ +#pragma once + +#include + +#include "Core/Base/SM_RingBuffer.h" +#include "Core/Serial/Serial.h" + +#include "Core/Statistics/Frequency_Limit.h" +#include "Core/Statistics/Statistics.h" +#include "Core/transmit_protocol/core/statistics.h" +#include "Database.h" + +#include "Local_Server/global_include.h" +#include "global.h" +#include "PlayBack.h" + +namespace Psc { +class SM_RingBuffer; +} +// 统一封装数据输出的模式 +class Data_Source; +std::shared_ptr create_from_json(const Psc::JSON *that_json); + + +class Input_Format { +public: + Input_Format() { + test_v.resize(static_cast(SSR::Downlink_Format::Unknown)); + } + + Psc::JSON to_json() { + std::lock_guard g(mtx); + Psc::JSON ret = Psc::JSON::object(); + for (auto e : magic_enum::enum_values()) { + std::string_view value_view = magic_enum::enum_name(e); + std::string value(value_view.begin(), value_view.end()); + if (value == Psc::to_string(SSR::Downlink_Format::Unknown)) continue; + auto ev = static_cast(e); + bool it = test_v[ev]; + ret.append({value, it}); + } + return ret; + } + + void from_json(const Psc::JSON* json) { + std::lock_guard g(mtx); + for (auto e : magic_enum::enum_values()) { + std::string_view value_view = magic_enum::enum_name(e); + std::string value(value_view.begin(), value_view.end()); + if (value == Psc::to_string(SSR::Downlink_Format::Unknown)) continue; + auto use = json->get_bool(value); + auto ev = static_cast(e); + test_v[ev] = use; + } + } + + + bool test(SSR::Downlink_Format df) { + std::lock_guard g(mtx); + auto dfv = static_cast(df); + if (dfv >= static_cast(SSR::Downlink_Format::Unknown)) return false; + return test_v[dfv]; + } + + bool test(std::uint8_t dfv) { + std::lock_guard g(mtx); + if (dfv >= static_cast(SSR::Downlink_Format::Unknown)) return false; + return test_v[dfv]; + } +protected: + std::mutex mtx; + std::vector test_v; +}; + +class Data_Source : public DataBase, public std::enable_shared_from_this { +public: + ~Data_Source() override = default; + virtual std::string read() { return ""; } + std::shared_ptr that(); + virtual Psc::JSON get_custom_state_json() = 0; + Psc::JSON get_state(); + + + std::string last_char; // 用于处理奇数字节的情况 + + // std::shared_ptr parse_format; + Psc::Speed_Statistics read_speed; + Psc::Value_Statistics value_statistics; + Data_Source() { + // parse_format = std::make_shared(); + // 写入到配置文件是懒加载 其他保存时他跟着保存 + base_station.handle_when_updated = [this](SSR::HULC_Status_Message msg) { + if (this->update_form_gps) { + lat = msg.get_latitude(); + lon = msg.get_longitude(); + alt = msg.Alt; + } + }; + } + std::shared_ptr last_prase_msg = nullptr; + + + + + virtual void handle_mode_s(std::shared_ptr msg); + virtual void pre_handle(const std::string &data); + virtual std::shared_ptr create_msg(const std::shared_ptr& source, const std::string& packet); + virtual void update_msg_day_time(std::shared_ptr msg); + virtual void handle_mode_acs_source(); + + + void refresh_data_feed_key_list(); + void push_to_feed(const std::shared_ptr &msg); + std::atomic enable{}; + std::string type; + std::atomic base_station_show{}; + std::atomic aircraft_show{}; + + + std::atomic show_icao = true; + std::atomic show_call_sign = false; + std::atomic show_fly_status = false; + std::atomic keep_mode = true; + + + + double lat{}; + double lon{}; + double alt{}; + std::atomic update_form_gps{}; + std::string color = "#1677ff"; + int aircraft_pixel_size{}; + Mode_AC_Statistic_Data mode_ac_statistic; + Mode_S_Statistic_Data mode_s_statistic; + Frequency_Limit statistic_fl; + + virtual void handle_in_loop() {}; + std::string thread_key() const; + void test_and_attach_thread(); + void test_and_stop_thread() const; + Psc::JSON statistic_json() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(enable); + Ret_J(type); + ret.append({"飞机总数", get_aircraft_num()}); + ret.append({"解出位置的数量", have_pos_aircraft_num}); + ret.append({"mode_ac_statistic", mode_ac_statistic.to_json()}); + ret.append({"mode_s_statistic", mode_s_statistic.to_json()}); + ret.append({"all_connect_feed", get_all_connect_feed_status()}); + + + return ret; + } + virtual Psc::JSON to_json() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(key); + Ret_J(enable); + Ret_J(base_station_show); + Ret_J(aircraft_show); + Ret_J(color); + Ret_J(aircraft_pixel_size); + Ret_J(type); + Ret_J(lat); + Ret_J(lon); + Ret_J(alt); + Ret_J(update_form_gps); + Ret_J(show_icao); + Ret_J(show_call_sign); + Ret_J(show_fly_status); + Ret_J(keep_mode); + // ret.append({"parse_format", parse_format->to_json()}); + return ret; + } + virtual void from_json(const Psc::JSON *that_json) { + Get_J(key); + Get_J(enable); + Get_J(base_station_show); + Get_J(aircraft_show); + Get_J(color); + Get_J(aircraft_pixel_size); + Get_J(type); + Get_J(lat); + Get_J(lon); + Get_J(alt); + Get_J(update_form_gps); + Get_J(show_icao); + Get_J(show_call_sign); + Get_J(show_fly_status); + Get_J(keep_mode); + // parse_format->from_json(that_json->get("parse_format")); + } + bool open() { + if (!_open_) { + _open_ = _open(); + return _open_; + } + return true; + } + void close() { + if (_open_) { + _open_ = false; + _close(); + } + } + + struct Cached_Source_Info { + std::string key; + Psc::Value_Statistics mode_s_cache_num; + Psc::Value_Statistics mode_other_cache_num; + }; + + std::string buffer; + std::atomic need_refresh_data_feed_key_list = true; + Psc::JSON get_all_connect_feed_status(); + Psc::JSON get_all_connect_feed(); +protected: + std::mutex cdf_mtx; + std::vector cached_data_feed_key_list{}; + bool _open_ = false; + virtual bool _open() = 0; + virtual void _close() = 0; +}; + + +struct TCP_Client_Data_Source : Data_Source { + void handle_in_loop() override { + cli.tick(); + } + Psc::JSON get_custom_state_json() override { + auto& state = cli.state; + return VAR_JSON_1(state); + } + Psc::asio_socket::TCP_Client cli; + std::string ip; + std::uint16_t port{}; + + ~TCP_Client_Data_Source() override = default; + TCP_Client_Data_Source() { + type = "TCP_Client_Data_Source"; + } + bool _open() override { + Psc::asio_socket::Sockaddr_In addr; + addr.ip = ip; + addr.port = port; + + cli.set_dest_address(addr); + cli.create(); + return true; + } + void _close() override { cli.close(); } + + Psc::JSON to_json() override { + Psc::JSON ret = Data_Source::to_json(); + Ret_J(ip); + Ret_J(port); + return ret; + } + void from_json(const Psc::JSON *that_json) override { + Data_Source::from_json(that_json); + Get_J(ip); + Get_J(port); + } + + std::string read() override { + return cli.read(); + } +}; +struct Serial_Data_Source : Data_Source { + std::string port_name; + Baud_Rate_Type baud_rate{}; + Psc::JSON get_custom_state_json() override { + return Psc::JSON::object(); + } + Serial_Data_Source() { this->type = "Serial_Data_Source"; } + bool _open() override { + serial = new Psc::serial::Serial; + serial->set_serial_name(port_name); + serial->set_baud_rate(baud_rate); + serial->set_parity(Psc::serial::Parity::NoParity); + serial->set_data_bits(Psc::serial::DataBits::Data8); + serial->set_stop_bits(Psc::serial::StopBits::OneStop); + serial->set_flow_control(Psc::serial::FlowControl::HardwareControl); + serial->set_buffer_byte_size(10 * 1024); + bool ok = serial->open(); + if (!ok) { + std::cerr << "createSerial " + port_name + ":" + std::to_string(baud_rate) + " 打开串口失败!\n"; + } else { + // std::cerr << "createSerial " + serial_name + ":" + std::to_string(baud_rate) + " 打开串口成功!\n"; + } + return ok; + } + void _close() override { serial->close(); } + std::string read() override { return serial->read(); } + Psc::JSON to_json() override { + Psc::JSON ret = Data_Source::to_json(); + Ret_J(port_name); + Ret_J(baud_rate); + return ret; + } + void from_json(const Psc::JSON *that_json) override { + Data_Source::from_json(that_json); + Get_J(port_name) Get_J(baud_rate); + } + Psc::serial::Serial *serial{}; + ~Serial_Data_Source() override { + if (serial) { + serial->close(); + delete serial; + } + } +}; + + +enum class File_Data_Type { + SIMPLE_BIN_Blank, // 没有1a转义的 + BIN_Blank_Text, + AVR, + BIN_Text, + BIN_Blank_One_Line_With_Escape, + BIN_Blank_One_Line_No_Escape, + BIN, + Unknown + // 纯粹的二进制 +}; +enum Play_Mode { one, loop, analysis, time_batch }; + + +struct File_Data_Source : public Data_Source { + std::string file_path; + File_Data_Type data_type = File_Data_Type::BIN_Blank_Text; + Play_Mode play_mode = Play_Mode::one; + bool have_report_play_back_all_success = false; + std::string state = "null"; + Psc::JSON get_custom_state_json() override { + auto cur_virtual_time = player_clock.get_cur_time_point(); + auto playback_speed_rate = player_clock.playback_speed_rate; + return VAR_JSON_5(cur_virtual_time, playback_speed_rate, state, index, part_infos.size()); + } + File_Data_Source() { this->type = "File_Data_Source"; } + ~File_Data_Source() override = default; + + + + + struct Cache_Msg { + explicit Cache_Msg(std::shared_ptr msg) : msg(std::move(msg)) {} + SSR::Play_Back_Time_Point time() const { + return SSR::Play_Back_Time_Point{day_num, msg->mlat_timestamp.daysec}; + } + std::uint64_t day_num = 0; + std::shared_ptr msg; + }; + Player_Clock player_clock; + std::shared_ptr pre_cache = nullptr; + std::deque> cache_list; + + + + std::string get_true_file_path() const { return Psc::get_abs_path(file_path); } + void _close() override; + bool _open() override; + std::vector readBinaryFileAsString(const std::string& filepath, size_t part_size); + void from_json(const Psc::JSON *that_json) override { + Data_Source::from_json(that_json); + Get_J(file_path); + Get_J(data_type); + Get_J(play_mode); + } + + Psc::JSON to_json() override { + Psc::JSON ret = Data_Source::to_json(); + Ret_J(file_path); + Ret_J(data_type); + Ret_J(play_mode); + return ret; + } + std::string read() override; + void handle_mode_acs_source() override; + + protected: + std::optional get_raw_line(int &ret_index); + long long index = 0; + std::vector part_infos; + std::mutex mtx; +}; +struct Dll_Data_Source : Data_Source { + Psc::JSON get_custom_state_json() override { + auto ret = VAR_JSON_1(state); + ret.append({"read_vaild_len", vs.to_string()}); + return ret; + } + Psc::Value_Statistics vs; + std::string library_path; + std::string function_name; + File_Data_Type data_type = File_Data_Type::BIN_Blank_Text; + Dll_Data_Source() { this->type = "Dll_Data_Source"; } + ~Dll_Data_Source() override = default; + std::size_t buffer_size{}; + std::vector buffer; + void from_json(const Psc::JSON *that_json) override { + Data_Source::from_json(that_json); + Get_J(library_path); + Get_J(function_name); + Get_J(data_type); + Get_J(buffer_size); + buffer.resize(buffer_size); + } + Psc::JSON to_json() override { + Psc::JSON ret = Data_Source::to_json(); + Ret_J(library_path); + Ret_J(function_name); + Ret_J(data_type); + Ret_J(buffer_size); + return ret; + } + void *lib{}; + + using Func_Type = size_t (*)(char *buf, std::size_t max_len); + // using Func_Type = std::uint16_t (*)(char *buf, std::uint16_t max_len); + Func_Type read_func_ptr{}; + + + using Call_Back = void (*)(char *buf, std::size_t len); + using set_Call_back = void (*)(Call_Back); + + std::string state; + void _close() override { Psc::free_library(lib); } + bool _open() override { + { + auto r = Psc::try_load_library(Psc::get_abs_path(library_path)); + if (!r) { + state = r.error().message(); + return false; + } + lib = r.value(); + } + { + auto r = Psc::try_load_function(lib, function_name); + if (!r) { + state = r.error().message(); + std::cout << LOG_POS << " [" << function_name << "] 函数指针加载失败!" << std::endl; + return false; + } + read_func_ptr = (Func_Type) r.value(); + } + state = "加载成功"; + return true; + } + std::string read() override; +}; + + +struct Shared_Memory_Data_Source : Data_Source { + Psc::JSON get_custom_state_json() override { + return Psc::JSON::object(); + } + void from_json(const Psc::JSON *that_json) override { + Data_Source::from_json(that_json); + Get_J(shared_memory_name); + Get_J(shared_memory_size); + Get_J(data_type); + } + Psc::JSON to_json() override { + Psc::JSON ret = Data_Source::to_json(); + Ret_J(shared_memory_name); + Ret_J(shared_memory_size); + Ret_J(data_type); + return ret; + } + bool _open() override; + void _close() override; + std::string read() override; + +protected: + std::unique_ptr sm; + std::string shared_memory_name; + std::uint64_t shared_memory_size{}; + File_Data_Type data_type = File_Data_Type::BIN_Blank_Text; +}; + + +inline std::shared_ptr create_data_source_from_type(const std::string &t) { + std::shared_ptr ret{}; + if (t == "Serial_Data_Source") + ret = std::make_shared(); + else if (t == "TCP_Client_Data_Source") + ret = std::make_shared(); + else if (t == "File_Data_Source") + ret = std::make_shared(); + else if (t == "Dll_Data_Source") + ret = std::make_shared(); + else if (t == "Shared_Memory_Data_Source") + ret = std::make_shared(); + else { + std::cout << "未知的 Data_Source type类型!" << std::endl; + throw std::invalid_argument("unknown Data_Source type: " + t); + } + return ret; +} + + +struct Data_Source_Config { + Ordered_Map> map; + Data_Source_Config() = default; + void init(const Psc::JSON *that_json) { + auto list = that_json->get("list"); + for (auto &it: list->children) { + auto ds = create_from_json(&it); + map.push_back(ds); + } + } + Psc::JSON list() { + auto connect_json = Psc::JSON::array(); + for (auto &it: map.list()) { + connect_json.children.emplace_back(it->to_json()); + } + return connect_json; + } + Psc::JSON to_json() { + Psc::JSON ret = Psc::JSON::object(); + ret.append({"list", list()}); + return ret; + } + void server(Global *g); +}; + + diff --git a/module/Local_Server/Data_Source/Database.cpp b/module/Local_Server/Data_Source/Database.cpp new file mode 100644 index 0000000..23c39fd --- /dev/null +++ b/module/Local_Server/Data_Source/Database.cpp @@ -0,0 +1,339 @@ +#include "Database.h" +#include "../server/Global.h" +#include "../server/Performance_Monitor.h" + +#include +#include +#include + +Aircraft::Aircraft(const std::string &icao) : Aircraft_Info(icao) { + auto size = Global::instance()->mode_acs.max_track_point_size.load(); + air_pos_track_list.init(size); + surface_pos_track_list.init(size); +} + +void Aircraft::refresh_external_database_info() { + auto& manager = Global::instance()->external_resources_manager; + if (!external_database_info_loaded) { + external_database_info = manager.query_aircraft_external_databases(icao); + external_database_info_loaded = true; + } + + auto callsign = flight(); + if (!callsign) callsign = bds20_call_sign(); + if (last_external_database_callsign == callsign) { + return; + } + + callsign_external_database_info = + manager.query_callsign_external_databases(callsign); + last_external_database_callsign = std::move(callsign); +} + +std::shared_ptr DataBase::get_aircraft(const std::string& icao) { + return aircraft_map.get(icao); +} + + + + +std::shared_ptr DataBase::create_aircraft(const std::string& icao) { + auto ret = aircraft_map.create(icao); + ret->refresh_external_database_info(); + return ret; +} + +void DataBase::delete_timeout_aircraft() { + aircraft_map.remove_cond([](const std::shared_ptr& item) { + if (item->timestamp == 0) { + std::cout << "未初始化的 timestamp " << LOG_POS << std::endl; + } + long long time = std::time(nullptr) - item->timestamp; + return time > Global::instance()->mode_acs.timeout_seconds; + }); +} + + +JSON DataBase::get_aircraftlist() { + JSON json = JSON::array(); + for (auto& it : aircraft_map.values()) { + auto o = Aircraft_List_JSON_Service_O::to_That(this, it.get()); + json.children.push_back(o.toJson()); + } + return json; +} + +Psc::JSON DataBase::get_aircraftlist(int limit_msg_num) { + JSON json = JSON::array(); + for (auto& it : aircraft_map.values()) { + if (it->times < limit_msg_num) continue; + auto o = Aircraft_List_JSON_Service_O::to_That(this, it.get()); + json.children.push_back(o.toJson()); + } + return json; +} + +JSON DataBase::get_all_aircraft_json() { + JSON json = JSON::array(); + auto g = Global::instance(); + size_t num = 0; + for (auto& it : aircraft_map.values()) { + auto vto = Flight_VTO::to_VTO(it.get()); + bool have_pos = !it.get()->air_pos_track_list.empty(); + if (have_pos) { + json.children.push_back(vto.toJson()); + num++; + } + } + have_pos_aircraft_num = num; + return json; +} + + + + + +JSON DataBase::get_aircraft_list_after(time_t timestamp) { + JSON json = JSON::array(); + auto g = Global::instance(); + for (auto& it : aircraft_map.values()) { + auto vto = Flight_VTO::to_VTO(it.get()); + bool have_pos = !it.get()->air_pos_track_list.empty(); + if (have_pos && vto.time_stamp > timestamp) { + json.children.push_back(vto.toJson()); + } + } + return json; +} + +#ifdef Cache_Some_Mode_S +Psc::JSON DataBase::get_limit_mode_s_msg_info() { + + JSON ret = JSON::object(); + JSON list = JSON::array(); + std::map map; + std::map num_test; + for (auto& air : aircraft_map.values()) { + if (air->total_num > Cache_Some_Mode_S_Num) continue; + Psc::JSON air_json = Psc::JSON::object(); + { + Psc::JSON ms = Psc::JSON::array(); + int n = air->cache.size(); + for (auto msg : air->cache) { + Psc::JSON cur = Psc::JSON::object({ + {"hex", msg->msg_hex}, + {"df", to_string(msg->df)} + }); + if (msg->df == Downlink_Format::Extended_Squitter_17) { + + auto tc = type_code(msg->msg_bin); + cur.children.push_back({"tc", tc}); + map[tc]++; + num_test[air->total_num] = n; + } + ms.append(cur); + } + air_json.children.push_back({"icao", air->icao}); + air_json.children.push_back({"total_num", air->total_num}); + air_json.children.push_back({"ms", ms}); + } + list.children.push_back(air_json); + } + ret.children.push_back({"key", key}); + for (auto pair : map) { + ret.children.push_back({std::to_string(pair.first), pair.second}); + } + Psc::JSON t = Psc::JSON::object(); + for (auto pair : num_test) { + t.children.push_back({std::to_string(pair.first), pair.second}); + } + ret.children.push_back({"数量统计", t}); + ret.children.push_back({"list", list}); + return ret; +} +#endif + +size_t DataBase::get_aircraft_num() { return aircraft_map.size(); } + +#define _init_db \ +HTTP_REQUIRE_VALUE(data_source_key, params.try_get_string("data_source_key")) \ +auto db = Global::instance()->source(data_source_key); \ +if (db == nullptr) { \ + res->setStatusCode(drogon::k503ServiceUnavailable); \ + JSON ret; \ + res->setBody(ret.to_json_string()); \ + return; \ +} + + + +void database_server(Global* g) { + auto& svr = g->svr; + auto& api = g->api; + // g->load_aircraft_csv(g->mode_acs.aircraft_csv_path); + g->external_resources_manager.load_sqlite_db( + Psc::get_abs_path(g->external_resources_manager.ecap_sqlite_path)); + + +#ifdef Cache_Some_Mode_S + svr.Post(api + "get_cache_some_mode_s_info", [](HTTP_Param) { + CHECK_JSON_PARAM + _init_db + auto g = Global::instance(); + auto ret = JSON::object(); + if (db) { + ret = db->get_limit_mode_s_msg_info(); + } + res->setBody(warp(ret).to_json_string()); + }); +#endif + + svr.Post(api + "get_base_station_location", [](HTTP_Param) { + CHECK_JSON_PARAM + _init_db + auto g = Global::instance(); + auto ret = JSON::object(); + if (db) { + ret = db->base_station.to_Json(); + } + res->setBody(warp(ret).to_json_string()); + }); + svr.Post(api + "get_aircraft_base_info", [](HTTP_Param) { + CHECK_JSON_PARAM + _init_db + HTTP_REQUIRE_VALUE(icao, params.try_get_string("icao")) + auto aircraft = db->get_aircraft(icao); + JSON ret; + if (aircraft != nullptr) { + auto vto = Flight_VTO::to_VTO(aircraft.get()); + ret = vto.toJson(); + } + res->setBody(ret.to_json_string()); + }); + svr.Post(api + "get_aircraft_detail_info", [](HTTP_Param) { + CHECK_JSON_PARAM + _init_db + HTTP_REQUIRE_VALUE(icao, params.try_get_string("icao")) + auto aircraft = db->get_aircraft(icao); + JSON ret; + if (aircraft != nullptr) { + ret = aircraft->toJson(); + } + res->setBody(ret.to_json_string()); + }); + svr.Post(api + "get_aircraft_track_list", [](HTTP_Param) { + CHECK_JSON_PARAM + _init_db + HTTP_REQUIRE_VALUE(icao, params.try_get_string("icao")) + HTTP_REQUIRE_VALUE(last_size, params.try_get_number("last_size")) + auto aircraft = db->get_aircraft(icao); + JSON ret; + if (aircraft != nullptr) + { + ret = aircraft->air_pos_track_list.get_last_array_json(last_size); + ret.children.insert(ret.children.begin(), JSON("ds", data_source_key)); + ret.children.insert(ret.children.begin(), JSON("icao", icao)); + } + res->setBody(ret.to_json_string()); + }); + svr.Post(api + "get_aircraft_list_after", [](HTTP_Param) { + CHECK_JSON_PARAM + _init_db + HTTP_REQUIRE_VALUE(timestamp, params.try_get_number("timestamp")) + auto ret = db->get_aircraft_list_after(timestamp); + res->setBody(ret.to_json_string()); + }); + + svr.Post(api + "aircraft_change_list", [](HTTP_Param) { + CHECK_JSON_PARAM + _init_db + JSON ret = JSON::array(); + if (db) { + ret = db->get_all_aircraft_json(); + } else + { + + } + res->setBody(warp(ret).to_json_string()); + }); + + + svr.Get(R"(/aircraftlist.json)", [](HTTP_Param) { + auto list = Global::instance()->mode_acs.data_source_config.map.list(); + std::shared_ptr ts = nullptr; + auto ret = JSON::array(); + for (const auto& li : list) { + if (li->enable) { + ret = li->get_aircraftlist(); + break; + } + } + res->setBody(ret.to_json_string("", "\n", "")); + }); + + // aircraftlist.json 的带数据源版本 + svr.Post(api + "aircraft_list", [](HTTP_Param) { + CHECK_JSON_PARAM + auto g = Global::instance(); + HTTP_REQUIRE_VALUE(data_source_key, params.try_get_string("data_source_key")) + std::shared_ptr db; + { + db = g->source(data_source_key); + } + if (db == nullptr) { + res->setStatusCode(drogon::k503ServiceUnavailable); + JSON ret; + res->setBody(ret.to_json_string()); + return; + } + + + auto limit_msg_num = g->mode_acs.default_min_aircraft_list_num.load(); + auto t = params.get("limit_msg_num"); + if (t) { + HTTP_REQUIRE_VALUE(requested_limit_msg_num, t->try_number_val()) + limit_msg_num = requested_limit_msg_num; + } + JSON ret = JSON::array(); + { + Scope_Timer timer( + "aircraft_list.db_query", + g->http_monitor_config.slow_scope_threshold_ms); + ret = db->get_aircraftlist(limit_msg_num); + } + std::string body; + { + Scope_Timer timer( + "aircraft_list.json_serialization", + g->http_monitor_config.slow_scope_threshold_ms); + body = warp(ret).to_json_string(); + } + res->setBody(body); + }); + +} + + + + +static std::string output; + +static void log_i(const std::string& label, const std::string& value, + const std::string& unit = "") { + std::ostringstream oss; + // Format the label and value + oss << std::left << std::setw(30) << label << ": " << value; + // Add unit if not empty + if (!unit.empty()) { + oss << " " << unit << "\n"; + } else { + oss << "\n"; + } + // Append formatted string to output + output += oss.str(); +} + +static void log_i(const std::string& label, double value, + const std::string& unit = "") { + log_i(label, std::to_string(value), unit); +} diff --git a/module/Local_Server/Data_Source/Database.h b/module/Local_Server/Data_Source/Database.h new file mode 100644 index 0000000..4e8f39f --- /dev/null +++ b/module/Local_Server/Data_Source/Database.h @@ -0,0 +1,228 @@ +#pragma once +#include "BaseStation.h" +#include "../Aircraft/Aircraft.h" +#include "../Aircraft/Flight_VTO.h" +#include "../External_Database/export.h" +#include +#include +#include +#include +#include + + +template < + typename Key_Type, + typename Value_Type, + size_t Shard_Count = 64 +> +struct Data_Map { + using Ptr = std::shared_ptr; + + size_t size() const { + size_t ret = 0; + + for (const auto& shard : shards) { + std::shared_lock g(shard.mtx); + ret += shard.map.size(); + } + + return ret; + } + + Ptr get(const Key_Type& key) const { + const auto& shard = get_shard(key); + + std::shared_lock g(shard.mtx); + + auto iter = shard.map.find(key); + if (iter == shard.map.end()) { + return nullptr; + } + + return iter->second; + } + + Ptr create(const Key_Type& key) { + auto& shard = get_shard(key); + + { + std::shared_lock g(shard.mtx); + + auto iter = shard.map.find(key); + if (iter != shard.map.end()) { + return iter->second; + } + } + + // 构造对象放锁外,避免构造期间占锁 + auto new_value = std::make_shared(key); + + { + std::unique_lock g(shard.mtx); + + auto [iter, inserted] = shard.map.emplace(key, new_value); + + // 如果别的线程已经创建了,返回已有对象 + return iter->second; + } + } + + void remove(const Key_Type& key) { + auto& shard = get_shard(key); + + std::unique_lock g(shard.mtx); + shard.map.erase(key); + } + + std::vector values() const { + std::vector ret; + + for (const auto& shard : shards) { + std::shared_lock g(shard.mtx); + + for (const auto& [key, val] : shard.map) { + ret.push_back(val); + } + } + + return ret; + } + + template + std::vector get_cond(Cond&& cond) const { + std::vector ret; + + for (const auto& shard : shards) { + std::vector snapshot; + + { + std::shared_lock g(shard.mtx); + + snapshot.reserve(shard.map.size()); + + for (const auto& [key, val] : shard.map) { + snapshot.push_back(val); + } + } + + // cond 放锁外执行 + for (const auto& val : snapshot) { + if (cond(val)) { + ret.push_back(val); + } + } + } + + return ret; + } + + template + void remove_cond(Cond&& cond) { + for (auto& shard : shards) { + std::vector> snapshot; + + { + std::shared_lock g(shard.mtx); + + snapshot.reserve(shard.map.size()); + + for (const auto& [key, val] : shard.map) { + snapshot.emplace_back(key, val); + } + } + + std::vector> need_remove; + + // cond 放锁外执行 + for (const auto& [key, val] : snapshot) { + if (cond(val)) { + need_remove.emplace_back(key, val); + } + } + + if (need_remove.empty()) { + continue; + } + + { + std::unique_lock g(shard.mtx); + + for (const auto& [key, old_val] : need_remove) { + auto iter = shard.map.find(key); + if (iter == shard.map.end()) { + continue; + } + + // 防止同 key 已经被换成新对象,误删 + if (iter->second == old_val) { + shard.map.erase(iter); + } + } + } + } + } + + void delete_all() { + for (auto& shard : shards) { + std::unique_lock g(shard.mtx); + shard.map.clear(); + } + } + +protected: + struct Shard { + mutable std::shared_mutex mtx; + std::unordered_map map; + }; + + Shard& get_shard(const Key_Type& key) { + const size_t index = hasher(key) % Shard_Count; + return shards[index]; + } + + const Shard& get_shard(const Key_Type& key) const { + const size_t index = hasher(key) % Shard_Count; + return shards[index]; + } + +protected: + std::hash hasher; + Shard shards[Shard_Count]; +}; + + +class DataBase : public SSR::Data_Source_Interface { +public: + std::shared_ptr get_aircraft(const std::string& icao) override; + std::shared_ptr create_aircraft(const std::string& icao) override; + Psc::JSON get_aircraftlist(); + Psc::JSON get_aircraftlist(int limit_msg_num); + BaseStation base_station; + void delete_timeout_aircraft(); + std::string get_key() override { + return key; + } + std::string key; + std::atomic have_pos_aircraft_num{}; + Psc::JSON get_all_aircraft_json(); + Psc::JSON get_aircraft_list_after(time_t timestamp); +#ifdef Cache_Some_Mode_S + Psc::JSON get_limit_mode_s_msg_info(); +#endif + size_t get_aircraft_num(); + void clear_aircraft() { aircraft_map.delete_all(); } + void handle_HULC(const std::string& packet); + ~DataBase() override { + aircraft_map.delete_all(); + } + +protected: + + + Data_Map aircraft_map; +}; + + +struct Mode_S_Msg; +void database_server(Global* g); + diff --git a/module/Local_Server/Data_Source/PlayBack.cpp b/module/Local_Server/Data_Source/PlayBack.cpp new file mode 100644 index 0000000..60593e9 --- /dev/null +++ b/module/Local_Server/Data_Source/PlayBack.cpp @@ -0,0 +1 @@ +#include "PlayBack.h" diff --git a/module/Local_Server/Data_Source/PlayBack.h b/module/Local_Server/Data_Source/PlayBack.h new file mode 100644 index 0000000..230de4d --- /dev/null +++ b/module/Local_Server/Data_Source/PlayBack.h @@ -0,0 +1,33 @@ +#pragma once + +#include "SSR/Msg.h" + +#include + + +struct Player_Clock { + using clock = std::chrono::system_clock; + SSR::Play_Back_Time_Point start_time; // 抽象时间起点 + std::chrono::time_point true_start_time; // 真正开始回放的墙钟起点 + double playback_speed_rate = 20.0; + Player_Clock() { + true_start_time = clock::now(); + } + SSR::Play_Back_Time_Point get_cur_time_point() const noexcept { + + const auto now = clock::now(); + + // 防御:系统时间回拨或还没初始化导致 now < true_start_time + const double real_elapsed_sec = + std::max(0.0, std::chrono::duration(now - true_start_time).count()); + + const double sim_elapsed_sec = real_elapsed_sec * playback_speed_rate; + + // Time_Point 只有秒精度:向下取整 + const auto add = static_cast(sim_elapsed_sec); + + SSR::Play_Back_Time_Point tp = start_time; + tp.add_sec(add); + return tp; + } +}; diff --git a/module/Local_Server/Data_Source/export.h b/module/Local_Server/Data_Source/export.h new file mode 100644 index 0000000..a2939c1 --- /dev/null +++ b/module/Local_Server/Data_Source/export.h @@ -0,0 +1,5 @@ +#pragma once +#include "../Aircraft/aircraftlist_json.h" +#include "Data_Source.h" +#include "PlayBack.h" +#include "Database.h" \ No newline at end of file diff --git a/module/Local_Server/Data_Source/global.h b/module/Local_Server/Data_Source/global.h new file mode 100644 index 0000000..7517a08 --- /dev/null +++ b/module/Local_Server/Data_Source/global.h @@ -0,0 +1,38 @@ +#pragma once +#include "../../../../../core_library/Core/Core/Base/JSON.h" +#include "Core/socket/export.h" +#include "SSR/export.h" +#include +#include +#include +#include +#include +#include +#include + +// 用于debug 散乱的飞机从何而来 +// #define Cache_Some_Mode_S +#ifdef Cache_Some_Mode_S + #define Cache_Some_Mode_S_Num 20 +#endif + + + +#define Parse(t, Field) \ +auto& _##t = info.Field.value; \ +info.Field.time = info.timestamp; \ +if (!_##t.has_value()) _##t = std::remove_reference_t::ValueType(); \ +_##t.value().parse(msg); \ +auto& t = _##t.value(); +// ret.append(NAME.value.has_value()? Psc::Json(STRINGIFY(NAME##_time), NAME.time) : Psc::Json(STRINGIFY(NAME##_time), nullptr)); +#define STRINGIFY(x) #x +#define ADD_Json(NAME) ret.children.emplace_back(STRINGIFY(NAME), NAME); +#define ADD_Json_RET(NAME) \ +if (NAME.has_value()) \ +{ \ +ret.children.emplace_back(STRINGIFY(NAME), NAME.value()); \ +} else \ +{ \ +ret.children.emplace_back(STRINGIFY(NAME), nullptr); \ +} + diff --git a/module/Local_Server/External_Database/Adsblol_Vrs_Standing_Data.cpp b/module/Local_Server/External_Database/Adsblol_Vrs_Standing_Data.cpp new file mode 100644 index 0000000..01e402b --- /dev/null +++ b/module/Local_Server/External_Database/Adsblol_Vrs_Standing_Data.cpp @@ -0,0 +1,18 @@ +// 数据来源:adsblol/vrs-standing-data。VRS standing-data 的镜像和转换项目。 +// 链接:https://github.com/adsblol/vrs-standing-data +// 在线表使用该项目发布的 routes.csv,以 callsign 为业务主键。 +// 官方更新要求:ADSB.lol 镜像说明导出文件每小时更新。本地告警策略:超过 1 小时提示更新。 + +#include "Resource_Utils.h" + +std::unique_ptr make_adsblol_vrs_standing_data_resource() { + return External_Database_Utils::make_header_csv_resource({ + "adsblol_vrs_standing_data_routes", + "adsblol_routes.csv", + "https://vrs-standing-data.adsb.lol/routes.csv", + "ADSB.lol 发布的 VRS standing-data 航线镜像,以 callsign 查询机场代码序列。", + 3600, + {"Callsign", "Code", "Number", "AirlineCode", "AirportCodes"}, + {"Callsign"} + }); +} diff --git a/module/Local_Server/External_Database/External_Database.cpp b/module/Local_Server/External_Database/External_Database.cpp new file mode 100644 index 0000000..151c8fa --- /dev/null +++ b/module/Local_Server/External_Database/External_Database.cpp @@ -0,0 +1,581 @@ +#include "global.h" + +#include "Resource_Utils.h" +#include "Resources.h" +#include "../server/Global.h" +#include "../server/Performance_Monitor.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +Psc::JSON external_resource_status_to_json(const External_Resource_Status& status) { + return Psc::JSON::object({ + {"name", status.name}, + {"row_count", status.row_count}, + {"downloaded", status.downloaded}, + {"imported", status.imported}, + {"message", status.message}, + }); +} + +Psc::JSON external_resource_status_list_to_json( + const std::vector& status_list) { + auto result = Psc::JSON::array(); + for (const auto& status : status_list) { + result.append(external_resource_status_to_json(status)); + } + return result; +} + +} // 命名空间 + +std::optional External_Database_Row::get(const std::string& column) const { + const auto iter = columns.find(column); + return iter == columns.end() ? std::nullopt : iter->second; +} + +Psc::JSON External_Database_Row::to_json() const { + auto result = Psc::JSON::object(); + for (const auto& [column, value] : columns) { + result.append({column, value ? Psc::JSON(*value) : Psc::JSON(nullptr)}); + } + return result; +} + +External_Resources::External_Resources(std::string name, + std::string cache_file_name, + std::string download_url, + std::string description, + const std::uint64_t update_interval_seconds) + : name(std::move(name)), + cache_file_name(std::move(cache_file_name)), + download_url(std::move(download_url)), + description(std::move(description)), + update_interval_seconds(update_interval_seconds) {} + + + + + + +void External_Resources::mark_updated() { + last_updated_at = std::time(nullptr); +} + +void External_Resources::clear_last_updated_at() { + last_updated_at = 0; +} + +std::size_t External_Resources::row_count(SQLite::Database& db) const { + return static_cast( + db.execAndGet("SELECT COUNT(*) FROM " + + External_Database_Utils::quote_identifier(name)).getInt64()); +} + +std::filesystem::path External_Resources::source_file( + const std::filesystem::path& cache_pos) const { + return cache_pos / cache_file_name; +} + +bool External_Resources::fetch(const std::filesystem::path& cache_pos, + const bool force_download) const { + const auto target_file = source_file(cache_pos); + if (!force_download) return false; + if (download_url.empty()) return false; + External_Database_Utils::download_to_file(download_url, target_file); + return true; +} + +External_Resource_Status External_Resources::update( + SQLite::Database& db, + const std::filesystem::path& cache_pos, + const bool force_download, + const bool only_when_empty) { + create_table(db); + External_Resource_Status result{name, row_count(db), false, false, ""}; + if (only_when_empty && result.row_count != 0) { + result.message = "table already populated"; + return result; + } + + std::filesystem::create_directories(cache_pos); + result.downloaded = fetch(cache_pos, force_download); + if (cache_file_name.empty()) { + result.message = "dynamic query required"; + return result; + } + const auto local_file = source_file(cache_pos); + if (!std::filesystem::exists(local_file)) { + result.message = download_url.empty() ? "offline import or dynamic query required" + : "cache file unavailable"; + return result; + } + + result.row_count = import_file(db, local_file); + result.imported = true; + result.message = "ok"; + mark_updated(); + return result; +} + +External_Resource_Status External_Resources::import_offline( + SQLite::Database& db, + const std::filesystem::path& source_file) { + create_table(db); + External_Resource_Status result{name, 0, false, false, ""}; + result.row_count = import_file(db, source_file); + result.imported = true; + result.message = "ok"; + mark_updated(); + return result; +} + +External_Resource_Status External_Resources::clear_table( + SQLite::Database& db, + const std::filesystem::path& cache_pos) { + SQLite::Transaction transaction(db); + db.exec("DROP TABLE IF EXISTS " + + External_Database_Utils::quote_identifier(name)); + create_table(db); + transaction.commit(); + + if (!cache_file_name.empty()) { + std::error_code error; + std::filesystem::remove(source_file(cache_pos), error); + if (error) { + throw std::runtime_error("Cannot remove cache file for resource " + name + + ": " + error.message()); + } + } + + clear_last_updated_at(); + return {name, 0, false, false, "table cleared"}; +} + +std::optional External_Resources::query_one( + SQLite::Database& db, + const std::vector& primary_key_values) const { + const auto& columns = primary_key_columns(); + if (columns.size() != primary_key_values.size()) { + throw std::invalid_argument("primary key value count mismatch for resource: " + name); + } + + std::string sql = "SELECT * FROM " + + External_Database_Utils::quote_identifier(name) + " WHERE "; + for (std::size_t index = 0; index < columns.size(); ++index) { + if (index != 0) sql += " AND "; + sql += External_Database_Utils::quote_identifier(columns[index]) + + " = ? COLLATE NOCASE"; + } + sql += " LIMIT 1"; + + SQLite::Statement statement(db, sql); + for (std::size_t index = 0; index < primary_key_values.size(); ++index) { + statement.bind(static_cast(index + 1), + External_Database_Utils::normalize_key(primary_key_values[index])); + } + if (!statement.executeStep()) return std::nullopt; + + External_Database_Row row; + for (int index = 0; index < statement.getColumnCount(); ++index) { + const auto name = statement.getColumnName(index); + if (statement.isColumnNull(index)) { + row.columns.emplace(name, std::nullopt); + } else { + row.columns.emplace(name, statement.getColumn(index).getString()); + } + } + return row; +} + +External_Resources_Manager::External_Resources_Manager() { + register_external_resource(make_tar1090_db_aircraft_csv_gz_resource()); + register_external_resource(make_wiedehopf_tar1090_db_resource()); + register_external_resource(make_mictronics_aircraft_database_resource()); + register_external_resource(make_opensky_aircraft_database_resource()); + register_external_resource(make_faa_aircraft_registry_resource()); + register_external_resource(make_icao_doc_8643_resource()); + register_external_resource(make_vrs_routes_resource()); + register_external_resource(make_vrs_airports_resource()); + register_external_resource(make_adsblol_vrs_standing_data_resource()); + register_external_resource(make_vradarserver_standing_data_resource()); + register_external_resource(make_opensky_flightdata_api_resource()); +} + +void External_Resources_Manager::init(const Psc::JSON* that_json) { + if (!that_json) return; + const auto list = that_json->get("list"); + if (!list) return; + std::lock_guard lock(mtx_); + for (const auto& config : list->children) { + const auto name = config.try_get_string("name"); + if (!name) continue; + for (const auto& resource : resources_) { + if (resource->name != *name) continue; + resource->init(&config); + break; + } + } + Get_J(ecap_sqlite_path) +} + +Psc::JSON External_Resources_Manager::to_json() const { + std::lock_guard lock(mtx_); + auto ret = Psc::JSON::object(); + Ret_J(ecap_sqlite_path) + auto list = Psc::JSON::array(); + for (const std::unique_ptr& resource : resources_) { + list.children.emplace_back(resource->to_json()); + } + ret.children.emplace_back(Psc::JSON{"list", list}); + return ret; +} + +void External_Resources_Manager::load_sqlite_db(std::string path) { + try { + db_ = std::make_unique( + path, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + } catch (SQLite::Exception& error) { + Psc::fail_fast(std::string("load_sqlite_db error: ") + error.what()); + } + + db_->exec("PRAGMA journal_mode=WAL;"); + db_->exec("PRAGMA synchronous=NORMAL;"); + initialize(*db_, std::filesystem::path(path).parent_path()); + sync_missing(*db_); +} + +std::vector +External_Resources_Manager::refresh_external_databases(const bool force_download) { + return refresh_all(require_db(), force_download); +} + +External_Resource_Status External_Resources_Manager::refresh_external_database( + const std::string& resource_name, + const bool force_download) { + return refresh_one(require_db(), resource_name, force_download); +} + +External_Resource_Status External_Resources_Manager::import_external_database( + const std::string& resource_name, + const std::filesystem::path& source_file) { + return import_offline(require_db(), resource_name, source_file); +} + +External_Resource_Status External_Resources_Manager::clear_external_database_table( + const std::string& resource_name) { + return clear_table(require_db(), resource_name); +} + +std::optional +External_Resources_Manager::query_external_database( + const std::string& resource_name, + const std::vector& primary_key_values) const { + return query_one(require_db(), resource_name, primary_key_values); +} + +std::map> +External_Resources_Manager::query_aircraft_external_databases( + const std::string& icao24) const { + return query_aircraft(require_db(), icao24); +} + +std::map> +External_Resources_Manager::query_callsign_external_databases( + const std::optional& callsign) const { + return query_callsign(require_db(), callsign); +} + +std::vector +External_Resources_Manager::external_database_status() const { + return status(require_db()); +} + +void External_Resources_Manager::server(Global* g) { + auto& svr = g->svr; + const auto& api = g->api; + + svr.Post(api + "get_external_database_config", [this](HTTP_Param) { + + + auto t = warp(to_json()).to_json_string(); + + res->setBody(t); + }); + svr.Post(api + "get_external_database_status", [this](HTTP_Param) { + res->setBody( + warp(external_resource_status_list_to_json(external_database_status())) + .to_json_string()); + }); + svr.Post(api + "refresh_external_database", [this, g](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(name, params.try_get_string("name")) + const auto result = refresh_external_database(name, true); + g->save(); + res->setBody(warp(external_resource_status_to_json(result)).to_json_string()); + }); + svr.Post(api + "refresh_external_databases", [this, g](HTTP_Param) { + const auto result = refresh_external_databases(true); + g->save(); + res->setBody(warp(external_resource_status_list_to_json(result)).to_json_string()); + }); + svr.Post(api + "import_external_database", [this, g](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(name, params.try_get_string("name")) + HTTP_REQUIRE_VALUE(source_file, params.try_get_string("source_file")) + const auto result = import_external_database(name, source_file); + g->save(); + res->setBody(warp(external_resource_status_to_json(result)).to_json_string()); + }); + svr.Post(api + "upload_external_database", [this, g](HTTP_Param) { + drogon::MultiPartParser parser; + if (parser.parse(req) != 0) { + throw_invalid_http_param("multipart"); + } + const auto name = parser.getOptionalParameter("name"); + if (!name || parser.getFiles().size() != 1) { + throw_invalid_http_param("name or file"); + } + + const auto& upload = parser.getFiles().front(); + const auto serial = std::chrono::steady_clock::now().time_since_epoch().count(); + auto extension = std::string(upload.getFileExtension()); + if (!extension.empty() && extension.front() != '.') { + extension.insert(extension.begin(), '.'); + } + const auto saved_name = + "external_database_" + std::to_string(serial) + extension; + if (upload.saveAs(saved_name) != 0) { + throw std::runtime_error("Cannot save uploaded external database file"); + } + + const auto source_file = + std::filesystem::path(drogon::app().getUploadPath()) / saved_name; + const auto result = import_external_database(*name, source_file); + g->save(); + res->setBody(warp(external_resource_status_to_json(result)).to_json_string()); + }); + svr.Post(api + "clear_external_database_table", [this, g](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(name, params.try_get_string("name")) + const auto result = clear_external_database_table(name); + g->save(); + res->setBody(warp(external_resource_status_to_json(result)).to_json_string()); + }); + svr.Post(api + "query_external_database", [this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(name, params.try_get_string("name")) + HTTP_REQUIRE_PTR(primary_key_values_json, params.get("primary_key_values")) + std::vector primary_key_values; + for (const auto& value : primary_key_values_json->children) { + if (value.valueType != Psc::JsonType::String) { + throw_invalid_http_param("primary_key_values"); + } + primary_key_values.push_back(value.val); + } + const auto row = query_external_database(name, primary_key_values); + res->setBody(row ? warp(row->to_json()).to_json_string() + : Psc::JSON(nullptr).to_json_string()); + }); +} + +void External_Resources_Manager::register_external_resource( + std::unique_ptr external_res) { + if (!external_res) throw std::invalid_argument("external resource is null"); + resources_.push_back(std::move(external_res)); +} + +void External_Resources_Manager::initialize(SQLite::Database& db, + std::filesystem::path cache_pos) { + std::lock_guard lock(mtx_); + cache_pos_ = std::move(cache_pos); + for (const auto& resource : resources_) resource->create_table(db); +} + +std::vector External_Resources_Manager::sync_missing( + SQLite::Database& db) { + return update_all(db, false, true); +} + +std::vector External_Resources_Manager::refresh_all( + SQLite::Database& db, + const bool force_download) { + return update_all(db, force_download, false); +} + +External_Resource_Status External_Resources_Manager::refresh_one( + SQLite::Database& db, + const std::string& resource_name, + const bool force_download) { + std::lock_guard lock(mtx_); + return get_resource(resource_name).update(db, cache_pos_, force_download, false); +} + +External_Resource_Status External_Resources_Manager::import_offline( + SQLite::Database& db, + const std::string& resource_name, + const std::filesystem::path& source_file) { + std::lock_guard lock(mtx_); + return get_resource(resource_name).import_offline(db, source_file); +} + +External_Resource_Status External_Resources_Manager::clear_table( + SQLite::Database& db, + const std::string& resource_name) { + std::lock_guard lock(mtx_); + return get_resource(resource_name).clear_table(db, cache_pos_); +} + +std::optional External_Resources_Manager::query_one( + SQLite::Database& db, + const std::string& resource_name, + const std::vector& primary_key_values) const { + std::lock_guard lock(mtx_); + return get_resource(resource_name).query_one(db, primary_key_values); +} + +std::map> +External_Resources_Manager::query_aircraft(SQLite::Database& db, + const std::string& icao24) const { + Scope_Timer timer( + "external_database.query_aircraft", + Global::instance()->http_monitor_config.slow_scope_threshold_ms); + static const std::vector kAircraftResourceKeys = { + "tar1090_db_aircraft", + "wiedehopf_tar1090_db_aircraft", + "mictronics_aircraft_database", + "opensky_aircraft_database", + "faa_aircraft_registry", + }; + std::lock_guard lock(mtx_); + std::map> result; + const auto add_row = [&](const std::string& resource_name, + const std::vector& primary_key_values, + const std::string& result_name = std::string{}) { + auto& resource = get_resource(resource_name); + auto row = resource.query_one(db, primary_key_values); + if (!row) return std::shared_ptr{}; + auto shared_row = std::make_shared(std::move(*row)); + result.emplace(result_name.empty() ? resource_name : result_name, shared_row); + return shared_row; + }; + + std::optional type_code; + for (const auto& resource_name : kAircraftResourceKeys) { + const auto row = add_row(resource_name, {icao24}); + if (row && !type_code) { + type_code = row->get("type_code"); + if (!type_code) type_code = row->get("typecode"); + } + } + + if (type_code) add_row("icao_doc_8643_aircraft_type_designators", {*type_code}); + return result; +} + +std::map> +External_Resources_Manager::query_callsign( + SQLite::Database& db, + const std::optional& callsign) const { + Scope_Timer timer( + "external_database.query_callsign", + Global::instance()->http_monitor_config.slow_scope_threshold_ms); + static const std::vector kRouteResourceKeys = { + "vrs_routes", + "adsblol_vrs_standing_data_routes", + "vradarserver_standing_data_routes", + }; + + std::lock_guard lock(mtx_); + std::map> result; + const auto add_row = [&](const std::string& resource_name, + const std::vector& primary_key_values, + const std::string& result_name = std::string{}) { + auto& resource = get_resource(resource_name); + auto row = resource.query_one(db, primary_key_values); + if (!row) return std::shared_ptr{}; + auto shared_row = std::make_shared(std::move(*row)); + result.emplace(result_name.empty() ? resource_name : result_name, shared_row); + return shared_row; + }; + + std::set airport_codes; + if (callsign && !callsign->empty()) { + for (const auto& resource_name : kRouteResourceKeys) { + const auto row = add_row(resource_name, {*callsign}); + if (!row) continue; + const auto airports = row->get("AirportCodes"); + if (!airports) continue; + + std::size_t start = 0; + while (start < airports->size()) { + const auto end = airports->find('-', start); + airport_codes.emplace(airports->substr(start, end - start)); + if (end == std::string::npos) break; + start = end + 1; + } + } + } + + for (const auto& airport_code : airport_codes) { + if (airport_code.empty()) continue; + add_row("vrs_airports", {airport_code}, "vrs_airports:" + airport_code); + } + return result; +} + +std::vector External_Resources_Manager::status( + SQLite::Database& db) const { + std::lock_guard lock(mtx_); + std::vector result; + for (const auto& resource : resources_) { + result.push_back({resource->name, resource->row_count(db), false, false, ""}); + } + return result; +} + +SQLite::Database& External_Resources_Manager::require_db() const { + if (!db_) { + throw std::runtime_error("DB not loaded. Call load_sqlite_db() first."); + } + return *db_; +} + +External_Resources& External_Resources_Manager::get_resource( + const std::string& resource_name) const { + for (const auto& resource : resources_) { + if (resource->name == resource_name) return *resource; + } + throw std::invalid_argument("unknown external resource: " + resource_name); +} + +std::vector External_Resources_Manager::update_all( + SQLite::Database& db, + const bool force_download, + const bool only_when_empty) { + std::lock_guard lock(mtx_); + std::vector result; + for (const auto& resource : resources_) { + try { + auto status = resource->update(db, cache_pos_, force_download, only_when_empty); + std::cout << "External database [" << status.name << "] " + << status.message << ", rows=" << status.row_count << std::endl; + result.push_back(std::move(status)); + } catch (const std::exception& error) { + std::cerr << "External database [" << resource->name + << "] update failed: " << error.what() << std::endl; + result.push_back( + {resource->name, resource->row_count(db), false, false, error.what()}); + } + } + return result; +} diff --git a/module/Local_Server/External_Database/Faa_Aircraft_Registry.cpp b/module/Local_Server/External_Database/Faa_Aircraft_Registry.cpp new file mode 100644 index 0000000..a5a342c --- /dev/null +++ b/module/Local_Server/External_Database/Faa_Aircraft_Registry.cpp @@ -0,0 +1,115 @@ +// 数据来源:FAA Aircraft Registry。美国 FAA 官方注册库,每日更新,MASTER.txt 包含 Mode-S Hex。 +// 链接:https://www.faa.gov/licenses_certificates/aircraft_certification/aircraft_registry/releasable_aircraft_download +// 下载:https://registry.faa.gov/database/ReleasableAircraft.zip +// 官方更新要求:FAA 下载页说明文件每日刷新。本地告警策略:超过 24 小时提示更新。 + +#include "../server/libarchive.h" +#include "Resource_Utils.h" + +#include + +#include +#include + +namespace { + +const std::vector kFaaColumns = { + "n_number", "serial_number", "aircraft_mfr_model_code", "engine_mfr_model_code", + "year_mfr", "registrant_type", "name", "street", "street2", "city", "state", + "zip_code", "region", "county", "country", "last_action_date", "cert_issue_date", + "certification", "type_aircraft", "type_engine", "status_code", "mode_s_code", + "fractional_ownership", "airworthiness_date", "other_names_1", "other_names_2", + "other_names_3", "other_names_4", "other_names_5", "expiration_date", + "unique_id", "kit_mfr", "kit_model", "icao24" +}; +const std::vector kFaaPrimaryKey = {"icao24"}; + +class Faa_Aircraft_Registry_Resource final : public External_Resources { +public: + Faa_Aircraft_Registry_Resource() + : External_Resources("faa_aircraft_registry", + "faa_registry/MASTER.txt", + "https://registry.faa.gov/database/ReleasableAircraft.zip", + "FAA 官方可公开下载的美国飞机注册库,MASTER.txt 包含 Mode-S Hex 注册信息。", + 86400) {} + + void create_table(SQLite::Database& db) const override { + db.exec(R"sql( + CREATE TABLE IF NOT EXISTS faa_aircraft_registry ( + n_number TEXT, serial_number TEXT, aircraft_mfr_model_code TEXT, + engine_mfr_model_code TEXT, year_mfr TEXT, registrant_type TEXT, + name TEXT, street TEXT, street2 TEXT, city TEXT, state TEXT, + zip_code TEXT, region TEXT, county TEXT, country TEXT, + last_action_date TEXT, cert_issue_date TEXT, certification TEXT, + type_aircraft TEXT, type_engine TEXT, status_code TEXT, + mode_s_code TEXT, fractional_ownership TEXT, airworthiness_date TEXT, + other_names_1 TEXT, other_names_2 TEXT, other_names_3 TEXT, + other_names_4 TEXT, other_names_5 TEXT, expiration_date TEXT, + unique_id TEXT, kit_mfr TEXT, kit_model TEXT, + icao24 TEXT NOT NULL PRIMARY KEY + ); + )sql"); + } + +protected: + bool fetch(const std::filesystem::path& cache_pos, + const bool force_download) const override { + const auto master_file = source_file(cache_pos); + if (!force_download) return false; + const auto zip_file = cache_pos / "ReleasableAircraft.zip"; + External_Database_Utils::download_to_file(download_url, zip_file); + unarchive_file(zip_file, cache_pos / "faa_registry"); + return true; + } + + std::size_t import_file(SQLite::Database& db, + const std::filesystem::path& source_file) const override { + auto master_file = source_file; + if (source_file.extension() == ".zip") { + const auto output_dir = source_file.parent_path() / "faa_registry"; + unarchive_file(source_file, output_dir); + master_file = output_dir / "MASTER.txt"; + } + + std::ifstream input(master_file, std::ios::binary); + if (!input) throw std::runtime_error("Cannot open FAA MASTER.txt: " + + master_file.string()); + + SQLite::Transaction transaction(db); + db.exec("DELETE FROM faa_aircraft_registry"); + SQLite::Statement statement( + db, External_Database_Utils::make_upsert_sql( + name, kFaaColumns, kFaaPrimaryKey)); + std::vector fields; + External_Database_Utils::read_csv_row(input, fields, ','); // 跳过表头。 + std::size_t row_count = 0; + while (External_Database_Utils::read_csv_row(input, fields, ',')) { + if (fields.size() < kFaaColumns.size()) fields.resize(kFaaColumns.size()); + const auto icao24 = External_Database_Utils::normalize_key(fields[33]); + if (icao24.empty()) continue; + + statement.reset(); + statement.clearBindings(); + for (std::size_t index = 0; index < kFaaColumns.size(); ++index) { + auto value = External_Database_Utils::normalize_field(fields[index]); + if (index == 33) value = icao24; + External_Database_Utils::bind_optional( + statement, static_cast(index + 1), value); + } + statement.exec(); + ++row_count; + } + transaction.commit(); + return row_count; + } + + [[nodiscard]] const std::vector& primary_key_columns() const override { + return kFaaPrimaryKey; + } +}; + +} // 命名空间 + +std::unique_ptr make_faa_aircraft_registry_resource() { + return std::make_unique(); +} diff --git a/module/Local_Server/External_Database/Icao_Doc_8643.cpp b/module/Local_Server/External_Database/Icao_Doc_8643.cpp new file mode 100644 index 0000000..b610b40 --- /dev/null +++ b/module/Local_Server/External_Database/Icao_Doc_8643.cpp @@ -0,0 +1,18 @@ +// 数据来源:ICAO Doc 8643 Aircraft Type Designators。用于把机型 designator 映射为制造商、型号和类别。 +// 链接:https://www.icao.int/operational-safety/doc-8643-aircraft-type-designators +// ICAO 页面没有稳定公开 CSV 地址;模块支持把人工下载的 CSV 离线导入。 +// 官方更新要求:ICAO 在每个 AIRAC 周期按月更新。本地告警策略:超过 28 天提示更新。 + +#include "Resource_Utils.h" + +std::unique_ptr make_icao_doc_8643_resource() { + return External_Database_Utils::make_header_csv_resource({ + "icao_doc_8643_aircraft_type_designators", + "icao_doc_8643.csv", + "", + "ICAO Doc 8643 机型代码表,用于将 A320、B738 等 designator 映射为制造商、型号和类别。", + 2419200, + {"type_designator", "manufacturer", "model", "description", "wtc"}, + {"type_designator"} + }); +} diff --git a/module/Local_Server/External_Database/Mictronics_Aircraft_Database.cpp b/module/Local_Server/External_Database/Mictronics_Aircraft_Database.cpp new file mode 100644 index 0000000..37d0fc4 --- /dev/null +++ b/module/Local_Server/External_Database/Mictronics_Aircraft_Database.cpp @@ -0,0 +1,13 @@ +// 数据来源:Mictronics Aircraft Database。社区维护的飞机静态信息库。 +// 链接:https://www.mictronics.de/aircraft-database/ +// 在线更新使用 tar1090-db 发布的 Mictronics 汇总镜像。 +// 官方更新要求:官网未声明固定更新频率。本地告警策略:超过 7 天提示检查。 + +#include "Resource_Utils.h" + +std::unique_ptr make_mictronics_aircraft_database_resource() { + return External_Database_Utils::make_tar1090_aircraft_resource( + "mictronics_aircraft_database", + "Mictronics 社区飞机数据库的 tar1090 汇总镜像,可按 ICAO24 查询注册号、机型和运营方。", + 604800); +} diff --git a/module/Local_Server/External_Database/OpenSky_Aircraft_Database.cpp b/module/Local_Server/External_Database/OpenSky_Aircraft_Database.cpp new file mode 100644 index 0000000..1863d4d --- /dev/null +++ b/module/Local_Server/External_Database/OpenSky_Aircraft_Database.cpp @@ -0,0 +1,24 @@ +// 数据来源:OpenSky Aircraft Database。字段较完整的 ICAO24 飞机静态库。 +// 链接:https://opensky-network.org/datasets/metadata/aircraftDatabase.csv +// 官方更新要求:下载页未声明固定更新频率。本地告警策略:超过 7 天提示检查。 + +#include "Resource_Utils.h" + +std::unique_ptr make_opensky_aircraft_database_resource() { + return External_Database_Utils::make_header_csv_resource({ + "opensky_aircraft_database", + "aircraftDatabase.csv", + "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv", + "OpenSky Network 飞机元数据 CSV,包含制造商、型号、注册号和运营方等字段。", + 604800, + { + "icao24", "registration", "manufacturericao", "manufacturername", "model", + "typecode", "serialnumber", "linenumber", "icaoaircrafttype", "operator", + "operatorcallsign", "operatoricao", "operatoriata", "owner", "testreg", + "registered", "reguntil", "status", "built", "firstflightdate", + "seatconfiguration", "engines", "modes", "adsb", "acars", "notes", + "categoryDescription" + }, + {"icao24"} + }); +} diff --git a/module/Local_Server/External_Database/OpenSky_FlightData_Api.cpp b/module/Local_Server/External_Database/OpenSky_FlightData_Api.cpp new file mode 100644 index 0000000..e983933 --- /dev/null +++ b/module/Local_Server/External_Database/OpenSky_FlightData_Api.cpp @@ -0,0 +1,30 @@ +// 数据来源:OpenSky FlightData API。按 ICAO24 和时间范围查询历史航班与起降机场。 +// 链接:https://openskynetwork.github.io/opensky-api/rest.html +// 该来源不是全量 CSV;表作为 API 查询结果缓存,主键为 ICAO24 与时间范围。 +// 官方更新要求:API 按请求提供数据并有调用限额,不执行全量定时下载。 +// 本地告警策略:动态缓存不显示周期性过期告警。 + +#include "Resource_Utils.h" + +std::unique_ptr make_opensky_flightdata_api_resource() { + return External_Database_Utils::make_schema_only_resource({ + "opensky_flightdata_api_cache", + "OpenSky FlightData API 查询缓存,按 ICAO24 和时间范围保存历史航班起降机场结果。", + 0, + R"sql( + CREATE TABLE IF NOT EXISTS opensky_flightdata_api_cache ( + icao24 TEXT NOT NULL, + begin_time TEXT NOT NULL, + end_time TEXT NOT NULL, + callsign TEXT, + est_departure_airport TEXT, + est_arrival_airport TEXT, + first_seen TEXT, + last_seen TEXT, + raw_json TEXT, + PRIMARY KEY (icao24, begin_time, end_time) + ); + )sql", + {"icao24", "begin_time", "end_time"} + }); +} diff --git a/module/Local_Server/External_Database/Resource_Utils.cpp b/module/Local_Server/External_Database/Resource_Utils.cpp new file mode 100644 index 0000000..1beeb29 --- /dev/null +++ b/module/Local_Server/External_Database/Resource_Utils.cpp @@ -0,0 +1,458 @@ +#include "Resource_Utils.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace External_Database_Utils { +namespace { + +constexpr const char* kTar1090Url = + "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz"; + +const std::vector kTar1090Columns = { + "icao24", "registration", "type_code", "subtype", + "type_name", "year", "operator_info" +}; +const std::vector kTar1090PrimaryKey = {"icao24"}; + +std::pair split_url(const std::string& url) { + const auto scheme_end = url.find("://"); + if (scheme_end == std::string::npos) { + throw std::runtime_error("Invalid download URL: " + url); + } + const auto path_begin = url.find('/', scheme_end + 3); + if (path_begin == std::string::npos) return {url, "/"}; + return {url.substr(0, path_begin), url.substr(path_begin)}; +} + +void replace_file(const std::filesystem::path& temp_file, + const std::filesystem::path& target_file) { + std::error_code error; + std::filesystem::remove(target_file, error); + error.clear(); + std::filesystem::rename(temp_file, target_file, error); + if (error) { + throw std::runtime_error("Cannot replace cache file " + target_file.string() + + ": " + error.message()); + } +} + +std::string create_csv_table_sql(const std::string& table_name, + const std::vector& columns, + const std::vector& primary_key_columns) { + std::ostringstream sql; + sql << "CREATE TABLE IF NOT EXISTS " << quote_identifier(table_name) << " ("; + for (std::size_t index = 0; index < columns.size(); ++index) { + if (index != 0) sql << ','; + sql << quote_identifier(columns[index]) << " TEXT"; + } + if (!primary_key_columns.empty()) { + sql << ", PRIMARY KEY ("; + for (std::size_t index = 0; index < primary_key_columns.size(); ++index) { + if (index != 0) sql << ','; + sql << quote_identifier(primary_key_columns[index]); + } + sql << ')'; + } + sql << ");"; + return sql.str(); +} + +class Header_Csv_Resource final : public External_Resources { +public: + explicit Header_Csv_Resource(Header_Csv_Resource_Config config) + : External_Resources(config.name, + config.cache_file_name, + config.download_url, + config.description, + config.update_interval_seconds), + config_(std::move(config)) {} + + void create_table(SQLite::Database& db) const override { + db.exec(create_csv_table_sql(name, config_.columns, + config_.primary_key_columns)); + } + +protected: + std::size_t import_file(SQLite::Database& db, + const std::filesystem::path& source_file) const override { + return import_header_csv(db, source_file, name, config_.columns, + config_.primary_key_columns, config_.delimiter); + } + + [[nodiscard]] const std::vector& primary_key_columns() const override { + return config_.primary_key_columns; + } + +private: + Header_Csv_Resource_Config config_; +}; + +class Schema_Only_Resource final : public External_Resources { +public: + explicit Schema_Only_Resource(Schema_Only_Resource_Config config) + : External_Resources(config.name, + "", + "", + config.description, + config.update_interval_seconds), + config_(std::move(config)) {} + + void create_table(SQLite::Database& db) const override { + db.exec(config_.create_table_sql); + } + +protected: + std::size_t import_file(SQLite::Database&, + const std::filesystem::path&) const override { + throw std::runtime_error("This resource is updated by its API-specific adapter"); + } + + [[nodiscard]] const std::vector& primary_key_columns() const override { + return config_.primary_key_columns; + } + +private: + Schema_Only_Resource_Config config_; +}; + +class Tar1090_Aircraft_Resource final : public External_Resources { +public: + Tar1090_Aircraft_Resource(std::string name, + std::string description, + const std::uint64_t update_interval_seconds) + : External_Resources(std::move(name), + "aircraft.csv", + kTar1090Url, + std::move(description), + update_interval_seconds) {} + + void create_table(SQLite::Database& db) const override { + db.exec(create_csv_table_sql(name, kTar1090Columns, kTar1090PrimaryKey)); + } + +protected: + bool fetch(const std::filesystem::path& cache_pos, + const bool force_download) const override { + const auto csv_file = source_file(cache_pos); + if (!force_download) return false; + const auto gzip_file = cache_pos / "aircraft.csv.gz"; + download_to_file(download_url, gzip_file); + decompress_gzip(gzip_file, csv_file); + return true; + } + + std::size_t import_file(SQLite::Database& db, + const std::filesystem::path& source_file) const override { + if (source_file.extension() == ".gz") { + auto csv_file = source_file; + csv_file.replace_extension(".csv"); + decompress_gzip(source_file, csv_file); + return import_file(db, csv_file); + } + + std::ifstream input(source_file, std::ios::binary); + if (!input) throw std::runtime_error("Cannot open CSV file: " + source_file.string()); + + SQLite::Transaction transaction(db); + db.exec("DELETE FROM " + quote_identifier(name)); + SQLite::Statement statement( + db, make_upsert_sql(name, kTar1090Columns, kTar1090PrimaryKey)); + std::vector fields; + std::size_t row_count = 0; + while (read_csv_row(input, fields, ';')) { + if (fields.empty()) continue; + const auto icao = normalize_key(fields[0]); + if (icao.empty()) continue; + + statement.reset(); + statement.clearBindings(); + statement.bind(1, icao); + for (std::size_t index = 1; index < kTar1090Columns.size(); ++index) { + std::optional value; + if (index < fields.size()) value = normalize_field(fields[index]); + bind_optional(statement, static_cast(index + 1), value); + } + statement.exec(); + ++row_count; + } + transaction.commit(); + return row_count; + } + + [[nodiscard]] const std::vector& primary_key_columns() const override { + return kTar1090PrimaryKey; + } +}; + +} // 命名空间 + +std::string quote_identifier(const std::string& value) { + std::string result = "\""; + for (const char ch : value) { + result += ch; + if (ch == '"') result += '"'; + } + result += '"'; + return result; +} + +std::string normalize_key(std::string value) { + const auto is_space = [](unsigned char ch) { return std::isspace(ch) != 0; }; + value.erase(value.begin(), std::find_if(value.begin(), value.end(), + [&](char ch) { return !is_space(ch); })); + value.erase(std::find_if(value.rbegin(), value.rend(), + [&](char ch) { return !is_space(ch); }).base(), + value.end()); + std::transform(value.begin(), value.end(), value.begin(), + [](unsigned char ch) { return static_cast(std::toupper(ch)); }); + return value; +} + +std::optional normalize_field(std::string value) { + const auto is_space = [](unsigned char ch) { return std::isspace(ch) != 0; }; + value.erase(value.begin(), std::find_if(value.begin(), value.end(), + [&](char ch) { return !is_space(ch); })); + value.erase(std::find_if(value.rbegin(), value.rend(), + [&](char ch) { return !is_space(ch); }).base(), + value.end()); + if (value.empty()) return std::nullopt; + return value; +} + +void bind_optional(SQLite::Statement& statement, + const int index, + const std::optional& value) { + if (value) statement.bind(index, *value); + else statement.bind(index); +} + +bool read_csv_row(std::istream& input, + std::vector& fields, + const char delimiter) { + fields.clear(); + std::string field; + char quote = '\0'; + bool have_data = false; + while (true) { + const int next = input.get(); + if (next == EOF) { + if (!have_data && field.empty() && fields.empty()) return false; + fields.push_back(std::move(field)); + return true; + } + have_data = true; + const char ch = static_cast(next); + if (quote != '\0') { + if (ch == quote) { + if (input.peek() == quote) { + input.get(); + field += quote; + } else { + quote = '\0'; + } + } else { + field += ch; + } + } else if ((ch == '"' || ch == '\'') && field.empty()) { + quote = ch; + } else if (ch == delimiter) { + fields.push_back(std::move(field)); + field.clear(); + } else if (ch == '\n') { + fields.push_back(std::move(field)); + return true; + } else if (ch != '\r') { + field += ch; + } + } +} + +std::string make_upsert_sql(const std::string& table_name, + const std::vector& columns, + const std::vector& primary_key_columns) { + std::ostringstream sql; + sql << "INSERT INTO " << quote_identifier(table_name) << " ("; + for (std::size_t index = 0; index < columns.size(); ++index) { + if (index != 0) sql << ','; + sql << quote_identifier(columns[index]); + } + sql << ") VALUES ("; + for (std::size_t index = 0; index < columns.size(); ++index) { + if (index != 0) sql << ','; + sql << '?'; + } + sql << ')'; + if (!primary_key_columns.empty()) { + sql << " ON CONFLICT ("; + for (std::size_t index = 0; index < primary_key_columns.size(); ++index) { + if (index != 0) sql << ','; + sql << quote_identifier(primary_key_columns[index]); + } + sql << ") DO UPDATE SET "; + bool first = true; + for (const auto& column : columns) { + if (std::find(primary_key_columns.begin(), primary_key_columns.end(), column) != + primary_key_columns.end()) continue; + if (!first) sql << ','; + first = false; + sql << quote_identifier(column) << "=excluded." << quote_identifier(column); + } + } + return sql.str(); +} + +void download_to_file(std::string url, const std::filesystem::path& target_file) { + std::filesystem::create_directories(target_file.parent_path()); + constexpr int kMaxRedirects = 5; + for (int redirect = 0; redirect <= kMaxRedirects; ++redirect) { + const auto [origin, path] = split_url(url); + auto client = drogon::HttpClient::newHttpClient(origin); + auto request = drogon::HttpRequest::newHttpRequest(); + request->setMethod(drogon::Get); + request->setPath(path); + request->addHeader("User-Agent", "ECAP_Server external database updater"); + const auto [result, response] = client->sendRequest(request, 600.0); + if (result != drogon::ReqResult::Ok || !response) { + throw std::runtime_error("Download failed: " + url); + } + const int status = static_cast(response->getStatusCode()); + if (status >= 300 && status < 400) { + const auto location = response->getHeader("location"); + if (location.empty()) throw std::runtime_error("Redirect has no location: " + url); + url = location.front() == '/' ? origin + location : location; + continue; + } + if (status != 200) { + throw std::runtime_error("Download returned HTTP " + std::to_string(status) + + ": " + url); + } + const auto temp_file = target_file.string() + ".tmp"; + std::ofstream output(temp_file, std::ios::binary | std::ios::trunc); + if (!output) throw std::runtime_error("Cannot write cache file: " + temp_file); + const auto body = response->getBody(); + output.write(body.data(), static_cast(body.size())); + output.close(); + if (!output) throw std::runtime_error("Cannot finish cache file: " + temp_file); + replace_file(temp_file, target_file); + return; + } + throw std::runtime_error("Too many redirects while downloading: " + url); +} + +void decompress_gzip(const std::filesystem::path& gzip_file, + const std::filesystem::path& target_file) { + const auto temp_file = target_file.string() + ".tmp"; + gzFile input = gzopen(gzip_file.string().c_str(), "rb"); + if (!input) throw std::runtime_error("Cannot open gzip file: " + gzip_file.string()); + std::ofstream output(temp_file, std::ios::binary | std::ios::trunc); + if (!output) { + gzclose(input); + throw std::runtime_error("Cannot write decompressed file: " + temp_file); + } + char buffer[64 * 1024]; + int size = 0; + while ((size = gzread(input, buffer, sizeof(buffer))) > 0) output.write(buffer, size); + const int close_result = gzclose(input); + output.close(); + if (size < 0 || close_result != Z_OK || !output) { + std::filesystem::remove(temp_file); + throw std::runtime_error("Cannot decompress gzip file: " + gzip_file.string()); + } + replace_file(temp_file, target_file); +} + +std::size_t import_header_csv(SQLite::Database& db, + const std::filesystem::path& source_file, + const std::string& table_name, + const std::vector& columns, + const std::vector& primary_key_columns, + const char delimiter) { + std::ifstream input(source_file, std::ios::binary); + if (!input) throw std::runtime_error("Cannot open CSV file: " + source_file.string()); + std::vector header; + if (!read_csv_row(input, header, delimiter)) { + throw std::runtime_error("CSV file is empty: " + source_file.string()); + } + if (!header.empty() && header.front().size() >= 3 && + static_cast(header.front()[0]) == 0xef && + static_cast(header.front()[1]) == 0xbb && + static_cast(header.front()[2]) == 0xbf) { + header.front().erase(0, 3); + } + std::map header_index; + for (std::size_t index = 0; index < header.size(); ++index) { + header_index.emplace(header[index], index); + } + for (const auto& column : columns) { + if (header_index.count(column) == 0) { + throw std::runtime_error("CSV header is missing column '" + column + "'"); + } + } + + SQLite::Transaction transaction(db); + db.exec("DELETE FROM " + quote_identifier(table_name)); + SQLite::Statement statement(db, make_upsert_sql(table_name, columns, primary_key_columns)); + std::vector fields; + std::size_t row_count = 0; + while (read_csv_row(input, fields, delimiter)) { + bool primary_key_ok = true; + for (const auto& column : primary_key_columns) { + const auto index = header_index.at(column); + if (index >= fields.size() || normalize_key(fields[index]).empty()) { + primary_key_ok = false; + break; + } + } + if (!primary_key_ok) continue; + statement.reset(); + statement.clearBindings(); + for (std::size_t index = 0; index < columns.size(); ++index) { + std::optional value; + const auto source_index = header_index.at(columns[index]); + if (source_index < fields.size()) value = normalize_field(fields[source_index]); + if (value && + std::find(primary_key_columns.begin(), primary_key_columns.end(), + columns[index]) != primary_key_columns.end()) { + value = normalize_key(*value); + } + bind_optional(statement, static_cast(index + 1), value); + } + statement.exec(); + ++row_count; + } + transaction.commit(); + return row_count; +} + +std::unique_ptr make_header_csv_resource( + Header_Csv_Resource_Config config) { + return std::make_unique(std::move(config)); +} + +std::unique_ptr make_schema_only_resource( + Schema_Only_Resource_Config config) { + return std::make_unique(std::move(config)); +} + +std::unique_ptr make_tar1090_aircraft_resource( + std::string name, + std::string description, + const std::uint64_t update_interval_seconds) { + return std::make_unique( + std::move(name), std::move(description), update_interval_seconds); +} + +} // 命名空间 External_Database_Utils diff --git a/module/Local_Server/External_Database/Resource_Utils.h b/module/Local_Server/External_Database/Resource_Utils.h new file mode 100644 index 0000000..928e62a --- /dev/null +++ b/module/Local_Server/External_Database/Resource_Utils.h @@ -0,0 +1,67 @@ +#pragma once + +#include "global.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace External_Database_Utils { + +struct Header_Csv_Resource_Config { + std::string name; + std::string cache_file_name; + std::string download_url; + std::string description; + std::uint64_t update_interval_seconds{}; + std::vector columns; + std::vector primary_key_columns; + char delimiter = ','; +}; + +struct Schema_Only_Resource_Config { + std::string name; + std::string description; + std::uint64_t update_interval_seconds{}; + std::string create_table_sql; + std::vector primary_key_columns; +}; + +std::string quote_identifier(const std::string& value); +std::string normalize_key(std::string value); +std::optional normalize_field(std::string value); +void bind_optional(SQLite::Statement& statement, + int index, + const std::optional& value); +bool read_csv_row(std::istream& input, + std::vector& fields, + char delimiter); +std::string make_upsert_sql(const std::string& table_name, + const std::vector& columns, + const std::vector& primary_key_columns); +void download_to_file(std::string url, const std::filesystem::path& target_file); +void decompress_gzip(const std::filesystem::path& gzip_file, + const std::filesystem::path& target_file); +std::size_t import_header_csv(SQLite::Database& db, + const std::filesystem::path& source_file, + const std::string& table_name, + const std::vector& columns, + const std::vector& primary_key_columns, + char delimiter = ','); + +std::unique_ptr make_header_csv_resource( + Header_Csv_Resource_Config config); +std::unique_ptr make_schema_only_resource( + Schema_Only_Resource_Config config); +std::unique_ptr make_tar1090_aircraft_resource( + std::string name, + std::string description, + std::uint64_t update_interval_seconds); + +} // 命名空间 External_Database_Utils diff --git a/module/Local_Server/External_Database/Resources.h b/module/Local_Server/External_Database/Resources.h new file mode 100644 index 0000000..ae3cfc9 --- /dev/null +++ b/module/Local_Server/External_Database/Resources.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class External_Resources; + +std::unique_ptr make_tar1090_db_aircraft_csv_gz_resource(); +std::unique_ptr make_wiedehopf_tar1090_db_resource(); +std::unique_ptr make_mictronics_aircraft_database_resource(); +std::unique_ptr make_opensky_aircraft_database_resource(); +std::unique_ptr make_faa_aircraft_registry_resource(); +std::unique_ptr make_icao_doc_8643_resource(); +std::unique_ptr make_vrs_routes_resource(); +std::unique_ptr make_vrs_airports_resource(); +std::unique_ptr make_adsblol_vrs_standing_data_resource(); +std::unique_ptr make_vradarserver_standing_data_resource(); +std::unique_ptr make_opensky_flightdata_api_resource(); diff --git a/module/Local_Server/External_Database/Tar1090_Db_Aircraft_Csv_Gz.cpp b/module/Local_Server/External_Database/Tar1090_Db_Aircraft_Csv_Gz.cpp new file mode 100644 index 0000000..a42c38e --- /dev/null +++ b/module/Local_Server/External_Database/Tar1090_Db_Aircraft_Csv_Gz.cpp @@ -0,0 +1,12 @@ +// 数据来源:tar1090-db aircraft.csv.gz。轻量飞机静态库,提供 ICAO24 到注册号、机型、年份和备注的映射。 +// 链接:https://github.com/wiedehopf/tar1090-db/raw/refs/heads/csv/aircraft.csv.gz +// 官方更新要求:项目未声明固定发布频率。本地告警策略:超过 7 天提示检查。 + +#include "Resource_Utils.h" + +std::unique_ptr make_tar1090_db_aircraft_csv_gz_resource() { + return External_Database_Utils::make_tar1090_aircraft_resource( + "tar1090_db_aircraft", + "wiedehopf/tar1090-db 发布的轻量 ICAO24 飞机静态库,提供注册号、机型、年份和运营方备注。", + 604800); +} diff --git a/module/Local_Server/External_Database/Vradarserver_Standing_Data.cpp b/module/Local_Server/External_Database/Vradarserver_Standing_Data.cpp new file mode 100644 index 0000000..3dd6fa2 --- /dev/null +++ b/module/Local_Server/External_Database/Vradarserver_Standing_Data.cpp @@ -0,0 +1,19 @@ +// 数据来源:vradarserver/standing-data。Virtual Radar Server 的上游 standing-data 项目。 +// 链接:https://github.com/vradarserver/standing-data +// 在线表使用公开镜像 routes.csv,以 callsign 为业务主键。 +// 官方更新要求:上游未声明固定频率;当前使用的 ADSB.lol 镜像每小时更新。 +// 本地告警策略:超过 1 小时提示更新。 + +#include "Resource_Utils.h" + +std::unique_ptr make_vradarserver_standing_data_resource() { + return External_Database_Utils::make_header_csv_resource({ + "vradarserver_standing_data_routes", + "vradarserver_routes.csv", + "https://vrs-standing-data.adsb.lol/routes.csv", + "Virtual Radar Server standing-data 的航线适配表;当前在线更新使用 ADSB.lol 公共镜像。", + 3600, + {"Callsign", "Code", "Number", "AirlineCode", "AirportCodes"}, + {"Callsign"} + }); +} diff --git a/module/Local_Server/External_Database/Vrs_Airports.cpp b/module/Local_Server/External_Database/Vrs_Airports.cpp new file mode 100644 index 0000000..d7a94d4 --- /dev/null +++ b/module/Local_Server/External_Database/Vrs_Airports.cpp @@ -0,0 +1,18 @@ +// 数据来源:VRS airports.csv。以机场 ICAO/Code 为主键,保存机场名称、国家、经纬度和高度。 +// 链接:https://vrs-standing-data.adsb.lol/airports.csv +// 官方更新要求:ADSB.lol 镜像说明导出文件每小时更新。本地告警策略:超过 1 小时提示更新。 + +#include "Resource_Utils.h" + +std::unique_ptr make_vrs_airports_resource() { + return External_Database_Utils::make_header_csv_resource({ + "vrs_airports", + "airports.csv", + "https://vrs-standing-data.adsb.lol/airports.csv", + "VRS 机场字典,提供机场代码、名称、国家、经纬度和高度。", + 3600, + {"Code", "Name", "ICAO", "IATA", "Location", "CountryISO2", + "Latitude", "Longitude", "AltitudeFeet"}, + {"Code"} + }); +} diff --git a/module/Local_Server/External_Database/Vrs_Routes.cpp b/module/Local_Server/External_Database/Vrs_Routes.cpp new file mode 100644 index 0000000..0202587 --- /dev/null +++ b/module/Local_Server/External_Database/Vrs_Routes.cpp @@ -0,0 +1,17 @@ +// 数据来源:VRS routes.csv。以 callsign 为主键,保存航空公司代码和机场代码序列。 +// 链接:https://vrs-standing-data.adsb.lol/routes.csv +// 官方更新要求:ADSB.lol 镜像说明导出文件每小时更新。本地告警策略:超过 1 小时提示更新。 + +#include "Resource_Utils.h" + +std::unique_ptr make_vrs_routes_resource() { + return External_Database_Utils::make_header_csv_resource({ + "vrs_routes", + "routes.csv", + "https://vrs-standing-data.adsb.lol/routes.csv", + "VRS 航线表,以 callsign 查询机场代码序列,用于推断起飞机场、经停机场和目的机场。", + 3600, + {"Callsign", "Code", "Number", "AirlineCode", "AirportCodes"}, + {"Callsign"} + }); +} diff --git a/module/Local_Server/External_Database/Wiedehopf_Tar1090_Db.cpp b/module/Local_Server/External_Database/Wiedehopf_Tar1090_Db.cpp new file mode 100644 index 0000000..5de4d08 --- /dev/null +++ b/module/Local_Server/External_Database/Wiedehopf_Tar1090_Db.cpp @@ -0,0 +1,12 @@ +// 数据来源:wiedehopf/tar1090-db 项目。该项目发布 aircraft.csv.gz,并汇总 Mictronics 飞机数据库。 +// 链接:https://github.com/wiedehopf/tar1090-db?tab=readme-ov-file +// 官方更新要求:项目未声明固定发布频率。本地告警策略:超过 7 天提示检查。 + +#include "Resource_Utils.h" + +std::unique_ptr make_wiedehopf_tar1090_db_resource() { + return External_Database_Utils::make_tar1090_aircraft_resource( + "wiedehopf_tar1090_db_aircraft", + "wiedehopf/tar1090-db 项目发布的飞机静态数据库镜像,供 tar1090 本地识别飞机。", + 604800); +} diff --git a/module/Local_Server/External_Database/export.h b/module/Local_Server/External_Database/export.h new file mode 100644 index 0000000..e31b58c --- /dev/null +++ b/module/Local_Server/External_Database/export.h @@ -0,0 +1,2 @@ +#pragma once +#include "global.h" diff --git a/module/Local_Server/External_Database/global.h b/module/Local_Server/External_Database/global.h new file mode 100644 index 0000000..03965ce --- /dev/null +++ b/module/Local_Server/External_Database/global.h @@ -0,0 +1,159 @@ +#pragma once + +#include "../../../../../core_library/Core/Core/Base/JSON.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Global; + +struct External_Database_Row { + std::map> columns; + + [[nodiscard]] std::optional get(const std::string& column) const; + [[nodiscard]] Psc::JSON to_json() const; +}; + +struct External_Resource_Status { + std::string name; + std::size_t row_count{}; + bool downloaded{}; + bool imported{}; + std::string message; +}; + +// 每个数据源模块只管理一张 SQLite 表。主键由数据源按业务含义选择: +// ICAO24、航班呼号、机场 ICAO 代码、机型代码或其他业务键。 +class External_Resources { +public: + External_Resources(std::string name, + std::string cache_file_name, + std::string download_url, + std::string description, + std::uint64_t update_interval_seconds); + virtual ~External_Resources() = default; + + + + [[nodiscard]] std::size_t row_count(SQLite::Database& db) const; + + virtual void create_table(SQLite::Database& db) const = 0; + External_Resource_Status update(SQLite::Database& db, + const std::filesystem::path& cache_pos, + bool force_download, + bool only_when_empty); + External_Resource_Status import_offline(SQLite::Database& db, + const std::filesystem::path& source_file); + External_Resource_Status clear_table(SQLite::Database& db, + const std::filesystem::path& cache_pos); + [[nodiscard]] std::optional query_one( + SQLite::Database& db, + const std::vector& primary_key_values) const; + void init(const Psc::JSON* that_json) + { + Get_J(name) + Get_J(download_url) + Get_J(description) + Get_J(update_interval_seconds) + Get_J(last_updated_at) + } + [[nodiscard]] Psc::JSON to_json() const + { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(name) + Ret_J(download_url) + Ret_J(description) + Ret_J(update_interval_seconds) + Ret_J(last_updated_at) + return ret; + } + std::string name; + std::string cache_file_name; + std::string download_url; + std::string description; + std::uint64_t update_interval_seconds{}; + std::time_t last_updated_at{}; +protected: + [[nodiscard]] virtual std::filesystem::path source_file( + const std::filesystem::path& cache_pos) const; + virtual bool fetch(const std::filesystem::path& cache_pos, + bool force_download) const; + virtual std::size_t import_file(SQLite::Database& db, + const std::filesystem::path& source_file) const = 0; + [[nodiscard]] virtual const std::vector& primary_key_columns() const = 0; + void mark_updated(); + void clear_last_updated_at(); + + +}; + +class External_Resources_Manager { +public: + External_Resources_Manager(); + + void init(const Psc::JSON* that_json); + [[nodiscard]] Psc::JSON to_json() const; + void server(Global* g); + void load_sqlite_db(std::string path); + std::vector refresh_external_databases(bool force_download = true); + External_Resource_Status refresh_external_database(const std::string& resource_name, + bool force_download = true); + External_Resource_Status import_external_database( + const std::string& resource_name, + const std::filesystem::path& source_file); + External_Resource_Status clear_external_database_table(const std::string& resource_name); + [[nodiscard]] std::optional query_external_database( + const std::string& resource_name, + const std::vector& primary_key_values) const; + [[nodiscard]] std::map> + query_aircraft_external_databases(const std::string& icao24) const; + [[nodiscard]] std::map> + query_callsign_external_databases( + const std::optional& callsign) const; + [[nodiscard]] std::vector external_database_status() const; + + void register_external_resource(std::unique_ptr external_res); + void initialize(SQLite::Database& db, std::filesystem::path cache_pos); + std::vector sync_missing(SQLite::Database& db); + std::vector refresh_all(SQLite::Database& db, + bool force_download = true); + External_Resource_Status refresh_one(SQLite::Database& db, + const std::string& resource_name, + bool force_download = true); + External_Resource_Status import_offline(SQLite::Database& db, + const std::string& resource_name, + const std::filesystem::path& source_file); + External_Resource_Status clear_table(SQLite::Database& db, + const std::string& resource_name); + [[nodiscard]] std::optional query_one( + SQLite::Database& db, + const std::string& resource_name, + const std::vector& primary_key_values) const; + [[nodiscard]] std::map> + query_aircraft(SQLite::Database& db, const std::string& icao24) const; + [[nodiscard]] std::map> + query_callsign(SQLite::Database& db, + const std::optional& callsign) const; + [[nodiscard]] std::vector status(SQLite::Database& db) const; + std::string ecap_sqlite_path; + + std::filesystem::path cache_pos_; +private: + [[nodiscard]] SQLite::Database& require_db() const; + External_Resources& get_resource(const std::string& resource_name) const; + std::vector update_all(SQLite::Database& db, + bool force_download, + bool only_when_empty); + std::unique_ptr db_; + std::vector> resources_; + mutable std::mutex mtx_; +}; diff --git a/module/Local_Server/State_Report/State_Report.cpp b/module/Local_Server/State_Report/State_Report.cpp new file mode 100644 index 0000000..3dd14a1 --- /dev/null +++ b/module/Local_Server/State_Report/State_Report.cpp @@ -0,0 +1,13 @@ +#include "State_Report.h" + +using namespace drogon; + +State_Report::State_Report() { + wm = Ws_Mgr::instance()->register_ws("/state_report"); +} + +void State_Report::push_state(Psc::JSON json) { + auto data = json.to_json_string(); + wm->push_data(data.data(), data.length()); +} + diff --git a/module/Local_Server/State_Report/State_Report.h b/module/Local_Server/State_Report/State_Report.h new file mode 100644 index 0000000..924beb4 --- /dev/null +++ b/module/Local_Server/State_Report/State_Report.h @@ -0,0 +1,22 @@ +#pragma once +#include "../global_include.h" +#include "../server/WebSocket_Manager.h" + +class State_Report : public Psc::Singleton { +public: + std::shared_ptr wm; + State_Report(); + void push_state(Psc::JSON json); +}; + + + +class With_State_Report { +public: + Psc::JSON state_json() { + return Psc::JSON::object(); + } + void report() { + State_Report::instance()->push_state(state_json()); + } +}; \ No newline at end of file diff --git a/module/Local_Server/global_include.h b/module/Local_Server/global_include.h new file mode 100644 index 0000000..d41fe26 --- /dev/null +++ b/module/Local_Server/global_include.h @@ -0,0 +1,419 @@ +#ifndef PSC_GLOBAL_INCLUDE_H +#define PSC_GLOBAL_INCLUDE_H +#include "Core/Statistics/Statistics.h" +#include "Core/socket/ByteOrder.h" +#include "SSR/export.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Global; + +class Invalid_Http_Param final : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; + +[[noreturn]] inline void throw_invalid_http_param(const char* detail) { + throw Invalid_Http_Param(detail); +} + + + +#define CHECK_JSON_PARAM \ +auto o_params = Psc::try_parse_json(req->body().data()); \ +if (!o_params.has_value()) { \ +throw_invalid_http_param("request body is not valid JSON"); \ +} \ +auto& params = o_params.value(); \ +auto that_json = ¶ms; + +#define HTTP_REQUIRE_VALUE(NAME, EXPRESSION) \ +auto o_http_##NAME = (EXPRESSION); \ +if (!o_http_##NAME.has_value()) { \ +throw_invalid_http_param(#NAME); \ +} \ +auto NAME = std::move(o_http_##NAME).value(); + +#define HTTP_REQUIRE_PTR(NAME, EXPRESSION) \ +auto NAME = (EXPRESSION); \ +if ((NAME) == nullptr) { \ +throw_invalid_http_param(#NAME); \ +} + +#define HTTP_REQUIRE_TRUE(EXPRESSION, DETAIL) \ +if (!(EXPRESSION)) { \ +throw_invalid_http_param(DETAIL); \ +} + + + + + +template +class Ordered_List { +public: + void swap(size_t i, size_t j) { + std::lock_guard g(mtx); + if (i >= _list.size() || j >= _list.size()) return; + + std::swap(_list[i], _list[j]); + } + + + + void push_back(Value_Type value) { + std::lock_guard g(mtx); + _list.push_back(value); // 添加默认值 + } + + void insert(int i, Value_Type value) { + std::lock_guard g(mtx); + _list.insert(_list.begin() + i, value); + } + + Value_Type get(size_t i) { + return _list[i]; + } + + void remove(size_t i) { + std::lock_guard g(mtx); + if (i >= _list.size()) return; + _list.erase(_list.begin() + i); + } + std::vector list(){ + std::lock_guard g(mtx); + return this->_list; + } + + void clear() { + list().clear(); + } +protected: + std::mutex mtx; + std::vector _list; +}; + + + +template +class Ordered_Map { +public: + + std::optional get(Key_Type key) { + std::lock_guard g(mtx); + auto it = _map.find(key); + if (it != _map.end()) { + return it->second; + } + return std::nullopt; + } + bool swap(size_t i, size_t j) { + std::lock_guard g(mtx); + if (i >= _list.size() || j >= _list.size()) return false; + + std::swap(_list[i], _list[j]); + + // 更新 map 中的 value,假设你可以从 Value 中获取 key + Key_Type key_i = _list[i]->key; + Key_Type key_j = _list[j]->key; + + _map[key_i] = _list[i]; + _map[key_j] = _list[j]; + return true; + } + + + + void push_back(Value_Type value) { + std::lock_guard g(mtx); + _list.push_back(value); // 添加默认值 + _map[value->key] = value; // 插入到 map + } + + bool insert(int i, Value_Type value) { + std::lock_guard g(mtx); + if (i < 0 || static_cast(i) > _list.size()) return false; + _list.insert(_list.begin() + i, value); + _map[value->key] = value; + return true; + } + + Value_Type get(size_t i) { + return _list[i]; + } + + std::optional try_get(size_t i) { + std::lock_guard g(mtx); + if (i >= _list.size()) return std::nullopt; + return _list[i]; + } + + bool remove(size_t i) { + std::lock_guard g(mtx); + if (i >= _list.size()) return false; + Key_Type& key = _list[i]->key; + _map.erase(key); + _list.erase(_list.begin() + i); + return true; + } + std::vector list(){ + std::lock_guard g(mtx); + return this->_list; + } + // std::map map(){ + // std::lock_guard g(mtx); + // return this->_map; + // } + void clear() { + list().clear(); + _map.clear(); + } +protected: + std::mutex mtx; + std::vector _list; + std::map _map; +}; + +class Mode_AC_Statistic_Data { +public: + Psc::JSON to_json() { + std::lock_guard g(mtx); + Psc::JSON ret = Psc::JSON::object(); + Ret_J(pre_second_num); + Ret_J(total_num); + Ret_J(length_error_num); + // Ret_J(speed); + return ret; + } + void add() { + std::lock_guard g(mtx); + add_inter(); + // speed.update(1); + } + void add_length_error() { + std::lock_guard g(mtx); + add_inter(); + length_error_num++; + } + +protected: + std::atomic length_error_num{}; + void add_inter() { + total_num++; + auto cur_time = std::time(nullptr); + if (cur_time != utc) + { + utc = cur_time; + pre_second_num = 0; + } + pre_second_num++; + } + std::mutex mtx; + size_t pre_second_num{}; + size_t total_num{}; + time_t utc{}; + // Psc::Speed_Statistics speed; +}; + + +class Statistic_Data { +public: + time_t utc{}; + size_t pre_second_num{}; + size_t total_num{}; + [[nodiscard]] Psc::JSON to_json() const { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(pre_second_num); + Ret_J(total_num); + return ret; + } + Statistic_Data() = default; +}; + +class DF_Statistic_Data { +public: + time_t utc{}; + size_t pre_second_num{}; + size_t total_num{}; + size_t crc_error_num{}; + Psc::JSON to_json() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(pre_second_num); + Ret_J(total_num); + Ret_J(crc_error_num); + // Ret_J(speed); + + std::string t = "null"; + if (total_num != 0) { + t = std::to_string(static_cast(crc_error_num)/static_cast(total_num)) + "%"; + } + ret.append({"crc误码率", t}); + for (auto& cur : statistic_map) + { + ret.append({cur.first, cur.second.to_json()}); + } + return ret; + } + void add() { + add_inter(); + } + void add_crc_error() { + crc_error_num++; + } + void add_sub_part(const std::string& key) { + add_sub_part_inter(key); + } + + // Psc::Speed_Statistics speed; +protected: + void add_inter() { + total_num++; + auto cur_time = std::time(nullptr); + if (cur_time != utc) + { + utc = cur_time; + pre_second_num = 0; + } + pre_second_num++; + // speed.update(1); + } + + void add_sub_part_inter(const std::string& key) { + auto t = get_create_statistic_data(key); + t->total_num++; + auto cur_time = std::time(nullptr); + if (cur_time != t->utc) + { + t->utc = cur_time; + t->pre_second_num = 0; + } + t->pre_second_num++; + } + Statistic_Data* get_create_statistic_data(const std::string& key) { + Statistic_Data* t; + auto iter = statistic_map.find(key); + if (iter == statistic_map.end()) + { + Statistic_Data cur{}; + statistic_map.insert(std::make_pair(key, cur)); + } + t = &statistic_map[key]; + return t; + } + + std::map statistic_map; +}; + +class Mode_S_Statistic_Data { +public: + // Psc::Speed_Statistics speed; + + // Psc::Speed_Statistics total_speed() const { + // Psc::Speed_Statistics total_speed{}; + // for (auto& cur : DF_Statistic_Data_map) { + // total_speed += cur.second.speed; + // } + // return total_speed; + // } + Psc::JSON to_json() { + size_t total_num = get_total_num(); + size_t crc_error_num = get_crc_error_num(); + Psc::JSON ret = Psc::JSON::object(); + + + ret.append({"每秒数量", get_total_pre_second_num()}); + + Ret_J(length_error_num); + Ret_J(total_num); + + std::string t = "null"; + if (total_num != 0) { + t = std::to_string(static_cast(crc_error_num)/static_cast(total_num)) + "%"; + } + Ret_J(crc_error_num); + ret.append({"crc_error_rate", t}); + + + + + Psc::JSON df_sub_type = Psc::JSON::object(); + for (auto& cur : DF_Statistic_Data_map) + { + df_sub_type.append({Psc::to_string(cur.first) + " [" + std::to_string(static_cast(cur.first)) + "]", cur.second.to_json()}); + } + Psc::JSON cpr_error_o = Psc::JSON::object(); + for (auto& cur : cpr_error) + { + cpr_error_o.append({Psc::to_string(cur.first) + " [" + std::to_string(static_cast(cur.first)) + "]", Psc::to_string(cur.second)}); + } + ret.append({"DF子类型", df_sub_type}); + ret.append({"CPR统计信息", cpr_error_o}); + return ret; + } + + void add_length_error(const std::string& key) { + length_error_num++; + } + + void add_cpr_error(SSR::CPR_Error_Type cet) { + ++cpr_error[cet]; + } + + DF_Statistic_Data* get_create_df_statistic_data(const SSR::Downlink_Format& key) { + DF_Statistic_Data* t; + auto iter = DF_Statistic_Data_map.find(key); + if (iter == DF_Statistic_Data_map.end()) + { + DF_Statistic_Data cur{}; + DF_Statistic_Data_map.insert(std::make_pair(key, cur)); + } + t = &DF_Statistic_Data_map[key]; + return t; + } + + size_t get_total_pre_second_num() { + size_t ret = 0; + for (auto& cur : DF_Statistic_Data_map) { + ret += cur.second.pre_second_num; + } + return ret; + } + + size_t get_total_num() { + size_t ret = 0; + for (auto& cur : DF_Statistic_Data_map) { + ret += cur.second.total_num; + } + return ret; + } + size_t get_crc_error_num() { + size_t ret = 0; + for (auto& cur : DF_Statistic_Data_map) { + ret += cur.second.crc_error_num; + } + return ret; + } + + +protected: + std::atomic length_error_num{}; + std::map DF_Statistic_Data_map; + std::map cpr_error; +}; + + +template +class Immutable : public T { + +}; + +#endif diff --git a/module/Local_Server/lib9002/DDCSampTable.h b/module/Local_Server/lib9002/DDCSampTable.h new file mode 100644 index 0000000..93b554b --- /dev/null +++ b/module/Local_Server/lib9002/DDCSampTable.h @@ -0,0 +1,28 @@ +#ifndef DDCSAMPTABLE_H +#define DDCSAMPTABLE_H +#include + +// adsb +const double KDDCSampTab0[11][2] = {{24000000, 1}, {12000000, 2}, {6000000, 4}, {3000000, 8}, {1500000, 16}, {750000, 32}, {375000, 64}, {187500, 128}, {93750, 256}, {46875, 512}, {23437, 1024}}; +const std::string KDDCSampTabStr0[11] = {"24000000 ", "12000000 ", "6000000 ", "3000000 ", "1500000 ", "750000 ", "375000 ", "187500 ", "93750 ", "46875 ", "23437 "}; +const int KDDCSampIndxMin0 = 0; +const int KDDCSampIndxMax0 = 10; +const double define_KDDCSampleMax0 = 24000000; +const double X0 = 0.8; +const int ZDDCSampIndxMin0 = 0; +const int ZDDCSampIndxMax0 = 12; +const double ZDDCSampTab0[13] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; +const double TXSFTab0[11] = {24000000, 12000000, 6000000, 3000000, 1500000, 750000, 375000, 187500, 93750, 46875, 23437}; + +// less 30 mhz +const double KDDCSampTab1[11][2] = {{61440000, 1}, {30720000, 2}, {15360000, 4}, {7680000, 8}, {3840000, 16}, {1920000, 32}, {960000, 64}, {480000, 128}, {240000, 256}, {120000, 512}, {60000, 1024}}; +const std::string KDDCSampTabStr1[11] = {"61440000", "30720000", "15360000", "7680000", "3840000", "1920000", "960000", "480000", "240000", "120000", "60000"}; +const int KDDCSampIndxMin1 = 0; +const int KDDCSampIndxMax1 = 10; +const double define_KDDCSampleMax1 = 61440000; +const double X1 = 0.8; +const int ZDDCSampIndxMin1 = 0; +const int ZDDCSampIndxMax1 = 12; +const double ZDDCSampTab1[13] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; +const double TXSFTab1[11] = {61440000, 30720000, 15360000, 7680000, 3840000, 1920000, 960000, 480000, 240000, 120000, 60000}; +#endif // DDCSAMPTABLE_H diff --git a/module/Local_Server/server/Config.cpp b/module/Local_Server/server/Config.cpp new file mode 100644 index 0000000..29720ea --- /dev/null +++ b/module/Local_Server/server/Config.cpp @@ -0,0 +1,192 @@ +#include "Config.h" +#include "../../../../../core_library/Core/Core/Base/Check.h" +#include "Core/Net_Adapter/Net_Adapter.h" +#include "Global.h" +#include +#undef interface + +std::string default_config_path; + + +std::string get_config_path() { +#ifdef WIN32 + return "D:/ae/proj/projects/ECAP_Server/config/config.json"; +#endif + static bool first = true; + if (!default_config_path.empty()) return default_config_path; + std::string ret; + std::ostringstream oss; + std::string file_name = "config.json"; + ret = get_exe_dir() + "/" + file_name; + if (first) + { + std::cout << oss.str() << std::endl; + first = false; + } + return ret; +} + + + + +void Mode_ACS_Config::server(Global* g) { + auto& svr = g->svr; + auto& api = g->api; + data_source_config.server(g); + data_feed_config.server(g); + source_feed_relation_config.server(g); + + svr.Post(api + "get_mode_s_basic_config", [this](HTTP_Param) { + CHECK_JSON_PARAM + res->setBody(warp(to_base_json()).to_json_string()); + }); + svr.Post(api + "set_mode_s_basic_config", [this](HTTP_Param) { + CHECK_JSON_PARAM + auto& j = o_params.value(); + init_base_json(&j); + res->setBody(warp(to_base_json()).to_json_string()); + }); +} + + +void Device_Config::server(Global* g) { + auto& svr = g->svr; + auto& api = g->api; + svr.Post(api + "get_device_config", [this](HTTP_Param) { + res->setBody(warp(to_json()).to_json_string()); + }); + + svr.Post(api + "get_net_adapter_list", [this](HTTP_Param) { + JSON ret = JSON::object(); + auto arr = JSON::array(); + for (auto& it : Psc::get_net_adapter_info_map()) + { + arr.append(it.second.to_Json()); + } + ret.append({"list", arr}); + res->setBody(warp(ret).to_json_string()); + }); + + + svr.Post(api + "set_device_config", [this](HTTP_Param) { + CHECK_JSON_PARAM + JSON old_device_config; + { + + old_device_config = to_json(); + } + HTTP_REQUIRE_PTR(net, params.get("net")) + Net_Config device, wifi; + bool has_device = false; + bool has_wifi = false; + for (const auto& it : net->children) + { + HTTP_REQUIRE_VALUE(net_key, it.try_get_string("key")) + if (net_key == "device") + { + device.init_from_json(&it); + has_device = true; + } + if (net_key == "wifi") + { + wifi.init_from_json(&it); + has_wifi = true; + } + } + HTTP_REQUIRE_TRUE(has_device && has_wifi, "net") + + + bool it1 = check_ip_netmask_gateway(device.ip, device.netmask, device.gateway); + bool it2 = check_ip_netmask_gateway(wifi.ip, wifi.netmask, wifi.gateway); + bool p1 = check_port(device.port); + bool p2 = check_port(wifi.port); + if (!it1 || !it2 || !p1 || !p2) + { + server_logger->error("", {}, LOG_POS + " ip port! " + device.ip + ":" + std::to_string(device.port)); + res->setBody(warp(old_device_config).to_json_string()); + return; + } + + +#if WIN32 + bool use = false; +#else + bool use = true; +#endif + { + + auto& w = *this; + *w.list.get("device").value() = device; + *w.list.get("wifi").value() = wifi; + } + Global::save(); + if (use) + { + std::string interface; + { + std::string path = get_exe_dir() + "/interface"; + std::ifstream ifs(path); + if (!ifs.is_open()) + { + server_logger->error("", {}, LOG_POS + " 无法打开文件! " + path); + res->setBody(warp(old_device_config).to_json_string()); + return; + } + std::stringstream buffer; + buffer << ifs.rdbuf(); + interface = buffer.str(); + } + static auto set = [](std::string& interface, const std::string& token, const std::string& value) { + std::string var_name = "${" + token + "}"; + auto size = var_name.size(); + size_t pos = interface.find(var_name); + while (pos != std::string::npos) + { + interface.replace(pos, size, value); + pos = interface.find(var_name); + } + }; + set(interface, "ip", device.ip); + set(interface, "gateway", device.gateway); + set(interface, "netmask", device.netmask); + set(interface, "wifi_ip", wifi.ip); + set(interface, "wifi_gateway", wifi.gateway); + set(interface, "wifi_netmask", wifi.netmask); + std::cout << interface << std::endl; + std::string path = "/etc/network/interfaces"; + std::ofstream outfile(path); + if (!outfile.is_open()) + { + server_logger->error("", {}, LOG_POS + " 无法打开文件!" + path); + res->setBody(warp(old_device_config).to_json_string()); + return; + } + outfile << interface; + outfile.close(); + std::string bash_path = get_exe_dir() + "/restart_net_service.sh"; + if (!std::filesystem::exists(bash_path)) + { + server_logger->error("", {}, LOG_POS + " !" + bash_path); + res->setBody(warp(old_device_config).to_json_string()); + return; + } + int result = system(bash_path.c_str()); + if (result != 0) + { + server_logger->error("", {}, LOG_POS + " 脚本执行失败!" + bash_path); + res->setBody(warp(old_device_config).to_json_string()); + return; + } + } + res->setBody(warp(params).to_json_string()); + }); +} + + +void Config::save() { + std::ofstream config_file; + config_file.open(get_config_path()); + std::string json = Global::instance()->toJson().to_json_string(); + config_file.write(json.c_str(), static_cast(json.size())); + config_file.close(); +} diff --git a/module/Local_Server/server/Config.h b/module/Local_Server/server/Config.h new file mode 100644 index 0000000..e872878 --- /dev/null +++ b/module/Local_Server/server/Config.h @@ -0,0 +1,357 @@ +#ifndef CONFIG_H_H +#define CONFIG_H_H +#include "../DSP/export.h" +#include "../Data_Feed/export.h" +#include "Core/spdlog/export.h" +#include "../Data_Source/export.h" +#include "MLAT.h" +#include "Source_Feed_Relation.h" + +#include +inline bool delete_log = false; + +extern std::string default_config_path; +std::string get_config_path(); + +struct Log_Config { + DELETE_COPY(Log_Config) + Log_Config() = default; + ~Log_Config() { + if (delete_log) std::cout << "~Log_Config()" << std::endl; + } + enum class Open_Mode { + Append, + Delete, + Rename + }; + Open_Mode open_mode = Open_Mode::Rename; + std::uint16_t piece_num{}; + std::uint16_t size_MB{}; + void init(const Psc::JSON* that_json) { + open_mode = Psc::to_enum(that_json->get_string("open_mode")); + Get_J(piece_num) + Get_J(size_MB) + } + [[nodiscard]] Psc::JSON to_json() const { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(open_mode); + Ret_J(piece_num); + Ret_J(size_MB); + return ret; + } +}; + +struct Console_Config { + DELETE_COPY(Console_Config) + Console_Config() = default; + ~Console_Config() { + if (delete_log) std::cout << "~Console_Config()" << std::endl; + } + bool record_playback; + bool mode_s_console; + bool post_request; + bool mlat_server; + void init(const Psc::JSON* that_json) { + Get_J(record_playback) + Get_J(mode_s_console) + Get_J(post_request) + Get_J(mlat_server) + } + [[nodiscard]] Psc::JSON to_json() const { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(record_playback) + Ret_J(mode_s_console) + Ret_J(post_request) + Ret_J(mlat_server) + return ret; + } +}; + +struct Http_Monitor_Config { + bool access_log = true; + std::size_t slow_request_threshold_ms = 1000; + std::size_t slow_scope_threshold_ms = 100; + + void init(const Psc::JSON* that_json) { + if (!that_json) return; + Get_J(access_log) + Get_J(slow_request_threshold_ms) + Get_J(slow_scope_threshold_ms) + } + + [[nodiscard]] Psc::JSON to_json() const { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(access_log) + Ret_J(slow_request_threshold_ms) + Ret_J(slow_scope_threshold_ms) + return ret; + } +}; + + + + + +struct Mode_ACS_Config { + DELETE_COPY(Mode_ACS_Config) + std::atomic timeout_seconds{}; + std::atomic max_track_point_size{}; + std::atomic max_speed_m_s{}; + std::atomic air_pos_timeout = 8; + std::atomic surface_pos_timeout = 32; + // static I air_time = 8; + // static I surface_time = 8 * 4; + std::atomic read_milliseconds{}; + std::atomic mode_s_max_num{}; + std::atomic mode_other_max_num{}; + std::atomic report_data_feed_msg_mum{}; + std::atomic Report_Data_Feed_Message_Num{}; + std::atomic default_min_aircraft_list_num{}; + + std::atomic ignore_df_11{}; + std::atomic monitor_msg_live{}; + + + + Data_feed_Config data_feed_config; + Data_Source_Config data_source_config; + Source_Feed_Relation_Config source_feed_relation_config; + Mode_ACS_Config() = default; + + ~Mode_ACS_Config() { + if(delete_log) std::cout << " ~Mode_S_Config()" << std::endl; + } + + + Psc::JSON to_base_json() const { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(timeout_seconds) + Ret_J(max_speed_m_s) + Ret_J(air_pos_timeout) + Ret_J(surface_pos_timeout) + Ret_J(read_milliseconds) + return ret; + } + + void init_base_json(const Psc::JSON* that_json) { + Get_J(timeout_seconds) + Get_J(max_speed_m_s) + Get_J(air_pos_timeout) + Get_J(surface_pos_timeout) + Get_J(read_milliseconds) + } + + + + + + + + void init(const Psc::JSON* that_json) { + init_base_json(that_json); + Get_J(max_track_point_size) + Get_J(mode_s_max_num) + Get_J(mode_other_max_num) + Get_J(report_data_feed_msg_mum) + Get_J(default_min_aircraft_list_num) + Get_J(ignore_df_11) + Get_J(monitor_msg_live) + + data_feed_config.init(that_json->get("data_feed")); + data_source_config.init(that_json->get("data_source")); + source_feed_relation_config.from_json(that_json->get("source_feed_relation")); + if (monitor_msg_live) { + SSR::Monitor_Mode_ACS_Message_Num = this->monitor_msg_live; + } + } + + Psc::JSON to_json() { + Psc::JSON ret = to_base_json(); + Ret_J(max_track_point_size) + Ret_J(mode_s_max_num) + Ret_J(mode_other_max_num) + Ret_J(report_data_feed_msg_mum) + Ret_J(default_min_aircraft_list_num) + Ret_J(ignore_df_11) + Ret_J(monitor_msg_live) + // Ret_J(aircraft_csv_path) + + ret.append(Psc::JSON("data_feed", data_feed_config.to_json())); + ret.append(Psc::JSON("data_source", data_source_config.to_json())); + ret.append(Psc::JSON("source_feed_relation", source_feed_relation_config.to_json())); + return ret; + } + void server(Global* g); +}; + + + +struct Web_Server_Config { + Web_Server_Config() = default; + [[nodiscard]] std::string get_true_webapp() const { + return Psc::get_abs_path(webapp); + } + [[nodiscard]] std::string get_true_tiles() const { + return Psc::get_abs_path(tiles); + } + Psc::JSON to_json() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(webapp) + Ret_J(tiles) + return ret; + } + void init(const Psc::JSON* that_json) { + Get_J(webapp) + Get_J(tiles) + } +protected: + std::string webapp; + std::string tiles; +}; + +struct Net_Config { + std::string key; + std::string ip; + std::uint16_t port{}; + std::string netmask; + std::string gateway; + Net_Config() = default; + [[nodiscard]] Psc::JSON to_json() const { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(key) + Ret_J(ip) + Ret_J(netmask) + Ret_J(gateway) + Ret_J(port) + return ret; + } + void init_from_json(const Psc::JSON* that_json) { + Get_J(key) + Get_J(ip) + Get_J(netmask) + Get_J(gateway) + Get_J(port) + } +}; + +struct Device_Config { + Ordered_Map list; + + ~Device_Config() { + if (delete_log) std::cout << "~Device_Config()" << std::endl; + for (auto it : list.list()) { + delete it; + } + list.clear(); + } + + + Psc::JSON to_json() { + Psc::JSON ret = Psc::JSON::object(); + Psc::JSON net = Psc::JSON::array(); + for (auto& li : list.list()) { + net.append(li->to_json()); + } + ret.append({"net", net}); + return ret; + } + + Device_Config() = default; + void init(const Psc::JSON* that_json) { + for (auto& li : that_json->get("net")->children) + { + auto cur = new Net_Config(); + cur->init_from_json(&li); + list.push_back(cur); + } + } + + Psc::JSON to_net_json() { + Psc::JSON ret = Psc::JSON::object(); + Psc::JSON net = Psc::JSON::array(); + for (auto& li : list.list()) { + net.append(li->to_json()); + } + ret.append({"net", net}); + return ret; + } + void server(Global* g); +}; + + + + +class Config { +public: + Config() = default; + DELETE_COPY(Config) + MLAT_Config mlat; + Device_Config device_config; + Web_Server_Config web_server_config; + Mode_ACS_Config mode_acs; + Log_Config log_config; + Console_Config console_config{}; + DSP_Config dsp_config; + Psc::JSON drogon_config; + Http_Monitor_Config http_monitor_config; + std::string version; + External_Resources_Manager external_resources_manager; + static Psc::JSON load() { + std::string config_path = get_config_path(); + + std::ifstream iso(config_path); + if (!iso.is_open()) + { + std::cerr << config_path << " can not open!" << std::endl; + Psc::fail_fast(); + } else { + std::cout << get_current_date_string() << " 启动加载配置文件: 【" << config_path << "】" << std::endl; + } + std::stringstream ss; + std::stringstream buffer; + buffer << iso.rdbuf(); + std::string fileContents = buffer.str(); + return Psc::parse_json(fileContents); + } + + void fromJson(Psc::JSON* that_json) { + Get_J(version) + mlat.init(that_json->get("mlat")); + web_server_config.init(that_json->get("web_server")); + device_config.init(that_json->get("device")); + mode_acs.init(that_json->get("mode_acs")); + console_config.init(that_json->get("console")); + dsp_config.init(that_json->get("dsp")); + drogon_config = *that_json->get("drogon"); + http_monitor_config.init(that_json->get("http_monitor")); + external_resources_manager.init(that_json->get("external_database")); + } + + Psc::JSON toJson() { + auto ret = Psc::JSON::object({ + {"version", version}, + {"dsp", dsp_config.to_json()}, + {"drogon", drogon_config}, + {"http_monitor", http_monitor_config.to_json()}, + {"mlat", mlat.to_json()}, + {"web_server", web_server_config.to_json()}, + {"device", device_config.to_json()}, + {"mode_acs", mode_acs.to_json()}, + //{"ais", ais.to_json()}, + {"log", log_config.to_json()}, + {"console", console_config.to_json()}, + {"external_database", external_resources_manager.to_json()}, + }); + + return ret; + } + static void save(); +}; + + + + + + + +#endif diff --git a/module/Local_Server/server/Global.cpp b/module/Local_Server/server/Global.cpp new file mode 100644 index 0000000..7c9a94a --- /dev/null +++ b/module/Local_Server/server/Global.cpp @@ -0,0 +1,385 @@ +#include "Global.h" +#include +#include +#include +#include +#include + + + + + +BaseLogger* server_logger = nullptr; +Debug_Logger_Info_Manager debug_logger_info_manager; + +void Global::handle_old_logs() { + auto mode = log_config.open_mode; + std::string dirPath = get_exe_dir() + "/" + "logs"; + namespace fs = std::filesystem; + try { + switch (mode) { + case Log_Config::Open_Mode::Rename: { + auto now = std::chrono::system_clock::now(); + auto in_time_t = std::chrono::system_clock::to_time_t(now); + std::ostringstream oss; + oss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d_%H-%M-%S"); + fs::path newDirPath = dirPath + "_" + oss.str(); + if (fs::exists(newDirPath)) { + std::cout << "New directory name already exists!" << std::endl; + return; + } + fs::rename(dirPath, newDirPath); + std::cout << "Directory renamed to: " << newDirPath << std::endl; + break; + } + case Log_Config::Open_Mode::Delete: { + if (fs::exists(dirPath)) { + fs::remove_all(dirPath); // 删除目录及其所有内容 + std::cout << "Directory and its contents deleted: " << dirPath << std::endl; + } + if (fs::create_directories(dirPath)) { + std::cout << "Directory created: " << dirPath << std::endl; + } else { + std::cout << "创建目录失败: " << dirPath << std::endl; + Psc::fail_fast(); + } + fs::permissions(dirPath, fs::perms::owner_all | fs::perms::group_all | fs::perms::others_all); + break; + } + case Log_Config::Open_Mode::Append: break; + default: std::cerr << "未知的打开模式!" << std::endl; + break; + } + } catch (const std::exception& ex) { + std::cerr << "Error: " + Psc::platform_2_utf8(ex.what()) + "\n"; + } +} + + +Global::Global() { + + + auto j = load(); + log_config.init(j.get("log")); + handle_old_logs(); + Base_Logger_Manager::instance()->set_thread_num(1).set_flush_seconds(3).init(); + int piece_num = log_config.piece_num; + int size_MB = log_config.size_MB; + int MB = 1024 * 1024; + asio_socket::socket_logger = new BaseLogger("@/logs/socket", MB * size_MB, piece_num); + serial::serial_logger = new BaseLogger("@/logs/serial", MB * size_MB, piece_num); + SSR::mode_s_logger = new BaseLogger("@/logs/mode_s", MB * size_MB * 10, piece_num); + dsp_logger = new BaseLogger("@/logs/dsp", MB * size_MB, piece_num); + //libais::libais_logger = new BaseLogger("@/logs/libais", MB * size_MB, piece_num); + server_logger = new BaseLogger("@/logs/server", MB * size_MB, piece_num); + asio_socket::socket_logger->set_redirect([](const BaseLogger::Log_Info& log_info) { + auto msg = "[" + std::string("func") + "~" + log_info.func_pattern + "] " + log_info.log_type.to_string() + + log_info.content; + debug_logger_info_manager.push_info(msg); + }); + serial::serial_logger->set_redirect([](const BaseLogger::Log_Info& log_info) { + auto msg = "[" + std::string("func") + "~" + log_info.func_pattern + "] " + log_info.log_type.to_string() + + log_info.content; + debug_logger_info_manager.push_info(msg); + }); + fromJson(&j); + +#ifdef Cache_Some_Mode_S + auto old_parse_ok_json = SSR::parse_ok_json; + SSR::parse_ok_json = [old_parse_ok_json](Aircraft_Info* aircraft_info, SSR::P_S mode_s_msg, Psc::JSON json) { + old_parse_ok_json(aircraft_info, mode_s_msg, json); + auto f = static_cast(aircraft_info); + f->add_msg_limit(mode_s_msg); + }; +#endif + + + SSR::cpr_cb = [](SSR::CPR_Error_Type t, const std::string& log, SSR::P_S msg) { + auto src = std::dynamic_pointer_cast(msg->source); + Mode_S_Statistic_Data* ss = &src->mode_s_statistic; + if (t != SSR::CPR_Error_Type::Normal) { + ss->add_cpr_error(t); + } + SSR::mode_s_logger->debug("CPR/"+ to_string(t), {}, log); + }; + // 处理错误 + SSR::parse_call_back = [this](SSR::Parse_Call_Back_Type type, SSR::Aircraft_Info* info, + const std::string& field_name, std::vector bds_list, void*, SSR::P_S msg) { + auto src = std::dynamic_pointer_cast(msg->source); + Mode_S_Statistic_Data* ss = &src->mode_s_statistic; + if (type == SSR::Parse_Call_Back_Type::Unknown_DF) { + //std::cout << "Unknown DF type!" << (int)(msg->df) << std::endl; + return; + } + DF_Statistic_Data* dfs = ss->get_create_df_statistic_data(msg->df); + dfs->add(); + if (type == SSR::Parse_Call_Back_Type::Normal_Block) { + dfs->add_sub_part(field_name); + info->update(msg); + } else if (type == SSR::Parse_Call_Back_Type::CRC_Error) { + dfs->add_crc_error(); + //std::cout << field_name << " :crc_error msg:" << msg->msg_hex << " df:" << (int)msg->df << std::endl; + } else if (type == SSR::Parse_Call_Back_Type::Length_Error) { + ss->add_length_error(field_name); + //std::cout << field_name << " :length_error msg:" << msg->msg_hex << " df:" << (int)msg->df << std::endl; + } + + //else if (type == Mode_S::Parse_Call_Back_Type::UNKNOWN_ERROR) {} else {} + }; + auto str = toJson().to_json_string(); + server_logger->debug("启动", {}, str); + SSR::mode_s_logger->debug("启动", {}, str); + //libais::libais_logger->debug("启动", {}, str); + std::cout << str << std::endl; + // libais::log = normal_log; + // libais::log = ais_log; + // mode_s_log_info = mode_s_log; + init_web_server(); +} +Global::~Global() { + std::cout << "Global::~Global()" << std::endl; +} + +void replace(std::string& originalStr, const std::string& findStr, const std::string& replaceStr) { + size_t pos = originalStr.find(findStr); + while (pos != std::string::npos) { + originalStr.replace(pos, findStr.length(), replaceStr); + pos = originalStr.find(findStr, pos + replaceStr.length()); + } +} +std::string oneLine(std::string str) { + replace(str, "\n", "[n]"); + return str; +} +bool isHexadecimal(const std::string& str) { + return std::all_of(str.begin(), str.end(), [](unsigned char c) { + return std::isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + }); +} +void handle_buffer_muti_start(std::string& buffer, const std::string& data, const std::set& prefix_list, + const std::function& callback) { + if (!data.empty()) { + buffer += data; + // std::cout << "[data]:" + data + "\n"; + // std::cout << "[buffer]:" + buffer + "\n"; + while (!buffer.empty() && buffer.back() == '\n') { + buffer.pop_back(); + } + while (true) { + std::string c; + size_t pos = 0, n = buffer.size(); + for (; pos < n; pos++) { + bool find = false; + for (const std::string& prefix : prefix_list) { + if (pos + prefix.size() <= n) { + if (buffer.substr(pos, prefix.size()) == prefix) { + c = prefix; + find = true; + break; + } + } + } + if (find) break; + } + if (pos == n) break; + std::string line = c + buffer.substr(0, pos); // 截取从开头到!之前的数据 + buffer = buffer.substr(pos + 1); // 更新buffer为剩余数据 + // std::cout << "[line]:" + line + "\n"; + if (pos != 0) callback(line); + } + } +} +void handle_buffer_head_tail(std::string& buffer, const std::string& data, const std::string& prefix, + const std::string& suffix, const std::function& callback) { + // 只在数据可用时进行读取 + if (!data.empty()) { + buffer += data; + size_t pos; + while ((pos = buffer.find_first_of(suffix)) != std::string::npos) { + size_t start = buffer.find_first_of(prefix); + if (start == std::string::npos) break; + std::string packet = buffer.substr(start + 1, pos - start - 1); + buffer.erase(0, pos + 1); + // 处理尾部空格 + while (!buffer.empty() && buffer.back() == '\n') { + buffer.pop_back(); + } + if (!packet.empty()) { + callback(packet); + } + } + } +} +void DataBase::handle_HULC(const std::string& packet) { + std::uint8_t len = SSR::get_len(packet); + std::uint8_t id = SSR::get_id(packet); + auto msg = mem2hex(packet); + + if (len != packet.size() - 4) { + SSR::mode_s_logger->debug("HULC/error_length", {}, + "需要: " + std::to_string(len) + " 当前: " + std::to_string(packet.size()) + " hex: " + + mem2hex(packet)); + return; + } + if (id == 1) { + // 状态消息 + auto status_msg = SSR::create_HULC_Status_Message(packet); + bool gps_ok = status_msg.GPS_device_detected() && status_msg.GPS_valid() && status_msg.GPS_has_valid_fix(); + if (gps_ok) { + + } + auto g = Global::instance(); + base_station.set_msg(status_msg); + Log_Type type({}, {{"msg", msg}}); + SSR::mode_s_logger->debug("HULC/status", type, status_msg.toJson().to_json_string()); + if (gps_ok) { + + } + } else if (id == 24) { + SSR::mode_s_logger->debug("HULC/reply", {}, msg); + } else { + SSR::mode_s_logger->debug("HULC/unknown_id", {}, msg); + } +} + +void read_ais_serial_data(std::atomic& running) { + // static int t = config.ais.read_serial_milliseconds; + // static bool ais_log = config.console_config.ais_serial; + // static bool use_ais_serial = !config.ais_mock.enable || config.ais_mock.use_real_serial; + // static auto lm = Light_Manager::instance(); + // serial::Serial* ais_serial{}; + // if (use_ais_serial) ais_serial = create_serial(config.ais.port_name, config.ais.baud_rate); + // libais::VdmStream stream; + // std::string ais_buffer; + // while (running.load(std::memory_order_acquire) == true) + // { + // std::string ais_data = use_ais_serial ? ais_serial->read() : mock_ais_serial.read(); + // if (!ais_data.empty()) + // { + // if (ais_log && !ais_data.empty()) + // { + // std::cout << "read:[ais_serial]:" << ais_data << std::endl; + // } + // libais::libais_logger->debug("ais_serial_read/ais_serial_read", {}, ais_data + "\n"); + // handle_buffer_muti_start(ais_buffer, ais_data, {"!", "$"}, [&](std::string& line) { + // lm->light_control(Light_Type::AIS, true); + // stream.AddLine(line); + // while (auto msg = stream.PopOldestMessage()) + // { + // parse_ais(msg.get()); + // } + // lm->light_control(Light_Type::AIS, false); + // }); + // } + // std::this_thread::sleep_for(std::chrono::milliseconds(t)); + // } + // delete ais_serial; +} +bool is_10004(SSR::Mode_S_Msg& msg) { + bool DF_11_17_18 = msg.df == SSR::Downlink_Format::All_Call_Reply_11 || msg.df == SSR::Downlink_Format::Extended_Squitter_17 || msg.df + == SSR::Downlink_Format::Extended_Squitter_Non_Transponder_18; + return DF_11_17_18; +} + +std::string to_file_name(std::string str) { + // 定义允许的合法字符 + auto is_valid_char = [](char c) { + return std::isalnum(c) || c == '_' || c == '-' || c == '.'; // 允许字母、数字、下划线、连字符、点 + }; + // 替换非法字符为下划线 '_' + std::transform(str.begin(), str.end(), str.begin(), [&](char c) { return is_valid_char(c) ? c : '_'; }); + // 检查结果字符串是否全是替换字符,防止生成全是下划线的文件名 + if (str.find_first_not_of('_') == std::string::npos) { + return "default_file_name"; // 返回一个默认文件名 + } + return str; +} +void Debug_Logger_Info_Manager::push_info(const std::string& c) { + std::lock_guard g(mtx); + std::string content = std::to_string(cur_serial_number) + ": " + c; + size_t content_length = content.size(); + info_list.push_back({cur_serial_number, content}); + current_total_length += content_length; + while (current_total_length > max_length && !info_list.empty()) { + current_total_length -= info_list.front().content.size(); + info_list.pop_front(); // 删除最早的记录 + } + cur_serial_number++; +} +Serial_Number_Type Debug_Logger_Info_Manager::get_cur_serial_number() { + std::lock_guard g(mtx); + return cur_serial_number; +} +JSON Debug_Logger_Info_Manager::to_json() { + JSON ret = JSON::object(); + ret.append({"cur_serial_number", cur_serial_number}); + return ret; +} +bool Web_Server::listen(const std::string &host, int port) { + drogon::HttpAppFramework* app = &drogon::app(); + auto g = Global::instance(); + Json::Value drogon_config; + { + auto jsonStr = g->drogon_config.to_json_string(); + std::cout << "drogon::app().run(); " << jsonStr << std::endl; + Json::CharReaderBuilder builder; + std::string errs; + auto reader = builder.newCharReader(); + if (!reader->parse(jsonStr.c_str(), jsonStr.c_str() + jsonStr.size(), &drogon_config, &errs)) { + // 解析失败 + std::cout << "drogon 配置文件加载失败!json格式解析失败!" << jsonStr << std::endl; + Psc::fail_fast(); + } + delete reader; + } + app->loadConfigJson(drogon_config); + app->disableSigtermHandling(); //关闭服务器信号处理 + app->addListener(host, port); + auto base_dir = get_exe_dir(); + // app->setLogPath(base_dir + "/logs/drogon.log"); + app->setUploadPath(base_dir + "/drogon_uploads"); + app->run(); + return true; +} + +std::string Debug_Logger_Info_Manager::get_info(Serial_Number_Type serial_number) { + std::lock_guard g(mtx); + std::string result; + if (serial_number == cur_serial_number) { + return ""; + } + // 从后向前遍历 data 列表 + for (auto it = info_list.rbegin(); it != info_list.rend(); ++it) { + // std::cout << "max_length: " << max_length << " size: " << info_list.size() << " serial_number " << + // serial_number << "cur_sequence" << it->sequence << " content.size" << it->content.size() << std::endl; + if (it->sequence >= serial_number) { + result = it->content + result; // 叠加 content + } else { + break; + } + } + return result; +} + + +std::string inet_address_to_string(const trantor::InetAddress &addr) +{ + char buf[INET6_ADDRSTRLEN] = {0}; + + if (!addr.isIpV6()) + { + // IPv4 + auto sa = reinterpret_cast(addr.getSockAddr()); + inet_ntop(AF_INET, &(sa->sin_addr), buf, sizeof(buf)); + uint16_t port = ntohs(sa->sin_port); + return std::string(buf) + ":" + std::to_string(port); + } + else + { + // IPv6 + auto sa6 = reinterpret_cast(addr.getSockAddr()); + inet_ntop(AF_INET6, &(sa6->sin6_addr), buf, sizeof(buf)); + uint16_t port = ntohs(sa6->sin6_port); + return "[" + std::string(buf) + "]:" + std::to_string(port); + } +} diff --git a/module/Local_Server/server/Global.h b/module/Local_Server/server/Global.h new file mode 100644 index 0000000..21e52be --- /dev/null +++ b/module/Local_Server/server/Global.h @@ -0,0 +1,239 @@ +#pragma once + + + +#include "Core/Base/ThreadManager.h" +#include "../Data_Source/export.h" +#include "../State_Report/State_Report.h" +#include "../global_include.h" +#include "Config.h" +#include + +#include "Config.h" + +#include "../../Radarcape_Core/cpp-httplib/httplib.h" +#include "../Data_Source/export.h" +#include "Core/Net_Adapter/Net_Adapter.h" +#include "libarchive.h" +#include +#include +#include + +// 集中 管理日志 +using namespace Psc; +extern BaseLogger* server_logger; +std::string to_file_name(std::string str); +std::string inet_address_to_string(const trantor::InetAddress &addr); + +using Serial_Number_Type = unsigned int; +struct Debug_Logger_Info_Manager { + void push_info(const std::string& content); + std::string get_info(Serial_Number_Type serial_number); + Serial_Number_Type get_cur_serial_number(); + struct Info { + Serial_Number_Type sequence; + std::string content; + }; + std::list info_list; + JSON to_json(); +private: + // 最大总长度 + static constexpr size_t max_length = 1000 * 300; + // 当前总长度 + size_t current_total_length = 0; + Serial_Number_Type cur_serial_number = 0; + std::mutex mtx; +}; +extern Debug_Logger_Info_Manager debug_logger_info_manager; + + + +#define RET_OK \ + res.status = StatusCode::OK_200; \ + res.set_content("{\"status\": 200}", "application/json"); + inline JSON warp(const JSON& data) { + // Json ret = Json::object(); + // ret.append({"status", 200}); + // ret.append({"data", data}); + // return ret; + return data; +} + + + +#define HTTP_Param const drogon::HttpRequestPtr& req, const drogon::HttpResponsePtr& res + +class Web_Server { +public: + std::string update = "update_"; + std::string insert = "insert_"; + std::string remove = "remove_"; + std::string search = "search_"; + std::string rise = "rise_"; + std::string fall = "fall_"; + using Handler = std::function; + bool listen(const std::string& host, int port); + + ~Web_Server() = default; + + + + + + static void stop() { + std::cout << "开始停止web服务" << std::endl; + drogon::app().quit(); + } + + Web_Server& Post(const std::string& pattern, Handler handler) { + drogon::app().registerHandler( + pattern, + [handler](const drogon::HttpRequestPtr& request, + std::function&& callback) { + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + + handler(request, resp); + + callback(resp); + }, + {drogon::Post} + ); + + return *this; + } + + Web_Server& Get(const std::string& pattern, Handler handler) { + drogon::app().registerHandlerViaRegex( + pattern, + [handler](const drogon::HttpRequestPtr& request, + std::function&& callback) { + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + + handler(request, resp); + + callback(resp); + }, + {drogon::Get} + ); + + return *this; + } +}; + + +// 这个对象不释放 + + + + + +class Mlat_MSG { +public: + explicit Mlat_MSG(const std::shared_ptr& first) { list.push_back(first);} + [[nodiscard]] std::optional icao() const { + auto a = list[0]; + auto t = a->type; + if (t == SSR::Mode_Msg::T::S7 || t == SSR::Mode_Msg::T::S14) { + return dynamic_cast(a.get())->icao; + } + return std::nullopt; + } + [[nodiscard]] std::string msg_hex() const { return list[0]->msg_hex;} + [[nodiscard]] SSR::Mode_Msg::T type() const { return static_cast(list[0]->type);} + [[nodiscard]] SSR::MLAT_timestamp& timestamp() const { return list[0]->mlat_timestamp;} + [[nodiscard]] std::shared_ptr data_source() const { return list[0]->source;} + void add(const std::shared_ptr& t) { + list.push_back(t); + } + [[nodiscard]] size_t size() const { return list.size(); } + std::vector> list; +}; + + + + + +class Mlat_Handler { +public: + std::list get_all() { + std::lock_guard gg(ok_mtx); + if (ok_list.empty()) return {}; + std::list ret; + std::swap(ok_list, ret); + return ret; + } + + void push(const std::shared_ptr& msg) { + std::lock_guard g(mtx); + + if (list.empty()) { + list.emplace_back(msg); + return; + } + + auto time = msg->mlat_timestamp; + auto p = list.rbegin(); + + while (p != list.rend()) { + auto cur_time = p->timestamp(); + // 往前最多找3秒 + if (cur_time.daysec + 3 < time.daysec) { + std::lock_guard gg(ok_mtx); + ok_list.splice(ok_list.end(), list, list.begin(), p.base()); + return; + } + if (p->msg_hex() == msg->msg_hex) { + p->add(msg); + return; + } + ++p; + } + list.insert(p.base(), Mlat_MSG(msg)); + } +protected: + std::list ok_list; + std::list list; + std::mutex ok_mtx; + std::mutex mtx; +}; + + + + + +class Global : public Config, public Singleton{ +public: + // asio::io_context ctx; + std::atomic init_ok{false}; + Global(); + ~Global(); + Detach_Thread_Manager thread_manager; + std::shared_ptr source(const std::string& data_source_key) { + return mode_acs.data_source_config.map.get(data_source_key).value_or(nullptr); + } + std::shared_ptr total_source() { + for (auto i : mode_acs.data_source_config.map.list()) + { + if (i->enable) return i; + } + return mode_acs.data_source_config.map.list().at(0); + } + Web_Server svr; + std::string api = "/api/"; + Mlat_Handler mlat_handler; + void init_web_server(); + void handle_old_logs(); + std::time_t start_server_time; + DELETE_COPY(Global) +}; + +void data_feed_thread(std::atomic& running); + + +void handle_buffer_muti_start(std::string& buffer, const std::string& data, const std::set& prefix_list, + const std::function& callback); +void handle_buffer_head_tail(std::string& buffer, const std::string& data, const std::string& prefix, + const std::string& suffix, const std::function& callback); + diff --git a/module/Local_Server/server/MLAT.cpp b/module/Local_Server/server/MLAT.cpp new file mode 100644 index 0000000..0e57948 --- /dev/null +++ b/module/Local_Server/server/MLAT.cpp @@ -0,0 +1,194 @@ +#include "MLAT.h" + +#include "Global.h" + +#include + +time_t datetime_to_timestamp(const std::string& datetime) { + std::tm tm = {}; + std::istringstream ss(datetime); + ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); + if (ss.fail()) { + throw std::runtime_error("时间格式错误"); + } + // std::mktime按本地时区转换 + return std::mktime(&tm); +} + +std::multimap hex_mlat_map; +std::vector read_lines() { + Base_Logger_Manager::instance()->init(); + + std::multimap ordered_multimap; + std::ifstream fin("C:/wyc/CLionProjects/plantSunCat/mainProjects/Radarcape/data/0701/info.txt"); + if (!fin) { + std::cerr << "无法打开文件!" << std::endl; + return {}; + } + std::string line; + while (std::getline(fin, line)) { + if (line.empty()) continue; + + std::vector fields; + std::stringstream ss(line); + std::string item; + while (std::getline(ss, item, ';')) { + fields.push_back(item); + } + // 检查字段数 + if (fields.size() < 9) continue; + // 2025-07-03 10:16:21;VG_MSDW;28069B;2025-06-27 08:00:14;A800103FFF9CCB27FFFCA89DE0D3;121.691398;37.344925;8000;;0000;2_1_3 + // 按 1-base 提取 + std::string icao = fields[2]; + icao.resize(6, '_'); + std::string msg = fields[4]; + std::string lon = fields[5]; + std::string lat = fields[6]; + + + + std::string time = fields[3]; + std::string hex = fields[4]; + // 输出(或者你可以存到 vector 里) + // std::cout << icao << " " << lat << " " << lon << " " << alt << std::endl; + + Base_MLAT_Data b; + //cur.icao = icao; + b.mlat_data.lon = stod(lon); + b.mlat_data.lat = stod(lat); + b.msg = line; + b.hex = hex; + b.icao = icao; + strncpy(b.mlat_data.icao, icao.c_str(), 6); + //std::cout << cur.lat << " " << cur.lon << std::endl; + // if (35 < cur.lat && cur.lat < 37 + // && + // 120 < cur.lon && cur.lon < 122 + // ) { + // + // pure_log("@/logs/mlat_.log", line + "\n"); + // } + ordered_multimap.insert({datetime_to_timestamp(time), b}); + + } + std::vector list; + for (auto it : ordered_multimap) { + Base_MLAT_Data& b = it.second; + list.push_back(b.mlat_data); + auto icao = std::string(b.mlat_data.icao, 6); + // if (icao == "78139F") { + // pure_log("@/logs/icao/" + icao + "_mlat_result.log", b.msg + "\n"); + // } + hex_mlat_map.insert({b.hex, b}); + } + return list; +} +std::vector all; + +void read(MLAT_Data* buf, std::uint16_t* len) { + if (all.empty()) { + all = read_lines(); + } + static size_t pos = 0; // 上一次拷贝结束后的位置 + const size_t BATCH = 100; + size_t total = all.size(); + size_t n = std::min(BATCH, total - pos); // 本次实际要拷贝多少 + + for (size_t i = 0; i < n; ++i) { + buf[i] = all[pos + i]; + } + *len = static_cast(n); + + pos += n; + if (pos >= total) { + pos = 0; // 如果需要循环一轮后归零,否则你可以不归零 + } +} + + +MLAT_Config::MLAT_Config() {} +void MLAT_Config::init(const Psc::JSON * that_json) { + enable = that_json->get_bool("enable"); + merge = that_json->get_bool("merge"); + library_path = that_json->get_string("library_path"); + function_name = that_json->get_string("function_name"); + if (enable) { + { + auto r = Psc::try_load_library(Psc::get_abs_path(library_path)); + if (!r) { + Psc::fail_fast(); + } + lib = r.value(); + } + { + auto r = Psc::try_load_function(lib, function_name); + if (!r) { + Psc::fail_fast(); + } + read_func_ptr = (Func_Type)r.value(); + } + + + all = read_lines(); + read_func_ptr = read; + } +} + + +void MLAT_Config::refresh() { + std::lock_guard guard(mtx); + std::uint16_t len{}; + if (read_func_ptr) read_func_ptr(buf, &len); + auto list = Global::instance()->mode_acs.data_source_config.map.list(); + for (std::uint16_t i = 0; i < len; i++) { + MLAT_Data& cur = buf[i]; + std::string icao = std::string(cur.icao, 6); + + for (const auto& li : list) { + //auto air = li->get_aircraft(icao); + auto air = li->get_aircraft(icao); + if (air) { + // air->mlat = true; + // air->air_pos_track_list.push(CPR::Position{cur.lat, cur.lon}); + } + } + + auto& t = update_map[icao]; + if (t.d.lat != cur.lat || t.d.lon != cur.lon) { + t.d = cur; + } + } +} + + +void MLAT_Config::server(Global* g) { + auto& svr = g->svr; + auto& api = g->api; + svr.Post(api + "get_mlat_list", [this](HTTP_Param) { + res->setBody(warp(get_reset()).to_json_string()); + }); + + // svr.Post(api + "get_mlat_server_config", [this](HTTP_Param) { + // res->setBody(warp(to_json()).to_json_string()); + // }); + // svr.Post(api + "/set_mlat_server_config", [this, g](HTTP_Param) { + // CHECK_JSON_PARAM + // MLAT_Server_Config t; + // t.init(&o_params.value()); + // bool ok = check_ipv4(t.ip) && check_port(t.port); + // if (!ok) + // { + // server_logger->error("", {}, LOG_POS + " ip port! " + t.ip + ":" + std::to_string(t.port)); + // res->setBody(warp(to_json()).to_json_string()); + // return; + // } + // + // auto& s = g->mlat_server; + // s.id = t.id; + // s.port = t.port; + // s.ip = t.ip; + // + // Global::save(); + // res->setBody(warp(to_json()).to_json_string()); + // }); +} diff --git a/module/Local_Server/server/MLAT.h b/module/Local_Server/server/MLAT.h new file mode 100644 index 0000000..6607d62 --- /dev/null +++ b/module/Local_Server/server/MLAT.h @@ -0,0 +1,80 @@ +#ifndef MLAT_H +#define MLAT_H +#include "../../../../../core_library/Core/Core/Base/JSON.h" +#include "../../../../../core_library/Core/Core/system/export.h" +#include "../global_include.h" +#include +#include + +class Global; +#pragma pack(push, 8) +extern "C" { + struct MLAT_Data { + char icao[6]; + double lon; + double lat; + }; +} +#pragma pack(pop) + + +struct MLAT_Store { + MLAT_Data d{}; + [[nodiscard]] Psc::JSON to_json() const { + Psc::JSON ret = Psc::JSON::object(); + ret.append({"icao", std::string(d.icao, 6)}); + ret.append({"lon", d.lon}); + ret.append({"lat", d.lat}); + return ret; + } + [[nodiscard]] std::string icao() const { + return {d.icao, 6}; + } +}; + +struct Base_MLAT_Data { + MLAT_Data mlat_data; + std::string msg; + std::string hex; + std::string icao; +}; + +extern std::multimap hex_mlat_map; + +struct MLAT_Config { + std::atomic enable{}; + std::atomic merge{}; + std::string library_path; + std::string function_name; + ~MLAT_Config() = default; + MLAT_Config(); + [[nodiscard]] Psc::JSON to_json() const { + Psc::JSON ret = Psc::JSON::object(); + ret.append(Psc::JSON("enable", this->enable)); + ret.append(Psc::JSON("merge", this->merge)); + Ret_J(library_path) + Ret_J(function_name) + return ret; + } + void* lib{}; + using Func_Type = void(*)(MLAT_Data* buf, std::uint16_t* len); + Func_Type read_func_ptr{}; + void init(const Psc::JSON* that_json); + MLAT_Data buf[std::numeric_limits::max()]{}; + void refresh(); + + Psc::JSON get_reset() { + std::lock_guard guard(mtx); + Psc::JSON ret = Psc::JSON::array(); + for (auto & t : update_map) { + ret.append(t.second.to_json()); + } + update_map.clear(); + return ret; + } + void server(Global* g); +protected: + std::mutex mtx; + std::map update_map; +}; +#endif diff --git a/module/Local_Server/server/Mode_Msg_Buffer.h b/module/Local_Server/server/Mode_Msg_Buffer.h new file mode 100644 index 0000000..fed97f7 --- /dev/null +++ b/module/Local_Server/server/Mode_Msg_Buffer.h @@ -0,0 +1,58 @@ +#ifndef BIN_Msg_Buffer_H +#define BIN_Msg_Buffer_H + +#include "../global_include.h" +#include + +struct Mode_Msg_Buffer { + void push(const std::shared_ptr& msg) { + std::lock_guard g(mtx); + msg_list_cache.push_back(msg); + } + + const std::vector>& get_all() { + std::lock_guard g(mtx); + std::swap(msg_list_cache, msg_list); + msg_list_cache.clear(); + mode_s_msg_num = 0; + mode_other_msg_num = 0; + return msg_list; + } + + size_t size() { + std::lock_guard g(mtx); + return msg_list_cache.size(); + } + std::atomic mode_s_msg_num{}; + std::atomic mode_other_msg_num{}; + Mode_Msg_Buffer() { + msg_list_cache.reserve(8 * 10240); + msg_list.reserve(8* 10240); + } +protected: + std::mutex mtx; + std::vector> msg_list_cache; + std::vector> msg_list; +}; + + + + +struct BIN_Msg_Buffer { + void push(const std::string& msg); + const std::vector& get_all(); + + + std::atomic mode_s_msg_num{}; + std::atomic mode_other_msg_num{}; + BIN_Msg_Buffer(); + + Psc::JSON state_json(); +protected: + std::mutex mtx; + std::vector msg_list_cache; + std::vector msg_list; +}; + + +#endif diff --git a/module/Local_Server/server/Performance_Monitor.h b/module/Local_Server/server/Performance_Monitor.h new file mode 100644 index 0000000..49e2ee1 --- /dev/null +++ b/module/Local_Server/server/Performance_Monitor.h @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +class Access_Logger { +public: + static void log(const drogon::HttpRequestPtr& request, + const drogon::HttpResponsePtr& response, + const double elapsed_ms) { + std::cout << "[http access] method=" << request->methodString() + << ", path=" << request->path() + << ", status=" << static_cast(response->statusCode()) + << ", cost_ms=" << elapsed_ms << std::endl; + } +}; + +class Slow_Request_Monitor { +public: + static void log_if_slow(const drogon::HttpRequestPtr& request, + const drogon::HttpResponsePtr& response, + const double elapsed_ms, + const std::size_t threshold_ms) { + if (elapsed_ms < static_cast(threshold_ms)) return; + std::cout << "[slow http request] method=" << request->methodString() + << ", path=" << request->path() + << ", status=" << static_cast(response->statusCode()) + << ", cost_ms=" << elapsed_ms + << ", threshold_ms=" << threshold_ms << std::endl; + } +}; + +class Scope_Timer { +public: + Scope_Timer(std::string name, const std::size_t threshold_ms) + : name_(std::move(name)), + threshold_ms_(threshold_ms), + start_(std::chrono::steady_clock::now()) {} + + ~Scope_Timer() { + const auto elapsed_ms = + std::chrono::duration( + std::chrono::steady_clock::now() - start_).count(); + if (elapsed_ms < static_cast(threshold_ms_)) return; + std::cout << "[slow scope] name=" << name_ + << ", cost_ms=" << elapsed_ms + << ", threshold_ms=" << threshold_ms_ << std::endl; + } + +private: + std::string name_; + std::size_t threshold_ms_; + std::chrono::steady_clock::time_point start_; +}; + +inline constexpr const char* kHttpRequestStart = "ecap.http_request_start"; + +inline void register_http_performance_monitor(const bool access_log, + const std::size_t slow_request_threshold_ms) { + drogon::app().registerSyncAdvice( + [](const drogon::HttpRequestPtr& request) -> drogon::HttpResponsePtr { + request->attributes()->insert(kHttpRequestStart, std::chrono::steady_clock::now()); + return {}; + }); + drogon::app().registerPreSendingAdvice( + [access_log, slow_request_threshold_ms]( + const drogon::HttpRequestPtr& request, + const drogon::HttpResponsePtr& response) { + const auto& start = + request->attributes()->get( + kHttpRequestStart); + if (start == std::chrono::steady_clock::time_point{}) return; + const auto elapsed_ms = + std::chrono::duration( + std::chrono::steady_clock::now() - start).count(); + if (access_log) Access_Logger::log(request, response, elapsed_ms); + Slow_Request_Monitor::log_if_slow( + request, response, elapsed_ms, slow_request_threshold_ms); + }); +} diff --git a/module/Local_Server/server/Source_Feed_Relation.cpp b/module/Local_Server/server/Source_Feed_Relation.cpp new file mode 100644 index 0000000..413aeeb --- /dev/null +++ b/module/Local_Server/server/Source_Feed_Relation.cpp @@ -0,0 +1,80 @@ +#include "Source_Feed_Relation.h" +#include "Config.h" +#include "Global.h" +void Source_Feed_Relation_Config::set_need_refresh() { + for (const auto& li : Global::instance()->mode_acs.data_source_config.map.list()) { + li->need_refresh_data_feed_key_list = true; + } +} + +void Source_Feed_Relation_Config::server(Global* g) { + auto& svr = g->svr; + auto& api = g->api; + std::string name = "source_feed_relation"; + + + + svr.Post(api + "get_" + name + "_config", [this](HTTP_Param) { + res->setBody(warp(to_json()).to_json_string()); + }); + + svr.Post(api + svr.search + name, [this, g](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(key, params.try_get_string("key")) + HTTP_REQUIRE_VALUE(relation, map.get(key)) + res->setBody(warp(relation->to_json()).to_json_string()); + }); + + + + svr.Post(api + svr.update + name, [this, g](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(key, params.try_get_string("key")) + HTTP_REQUIRE_VALUE(ds, map.get(key)) + ds->from_json(¶ms); + + // 直接关闭 + g->save(); + set_need_refresh(); + }); + + svr.Post(api + svr.insert + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_PTR(data, params.get("data")) + HTTP_REQUIRE_VALUE(t, data->try_get_string("type")) + std::cout << params.to_json_string() << std::endl; + auto ds = create_source_feed_relation_from_type(t); + ds->from_json(data); + HTTP_REQUIRE_TRUE(map.insert(index, ds), "index") + //std::cout << params.to_json_string() << std::endl; + res->setBody(warp(to_json()).to_json_string()); + g->save(); + set_need_refresh(); + }); + + svr.Post(api + svr.remove + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_TRUE(map.remove(index), "index") + g->save(); + res->setBody(warp(to_json()).to_json_string()); + set_need_refresh(); + }); + svr.Post(api + svr.rise + name, [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_TRUE(map.swap(index, index - 1), "index") + g->save(); + res->setBody(warp(to_json()).to_json_string()); + set_need_refresh(); + }); + svr.Post(api + svr.fall + name , [g, this](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(index, params.try_get_number("index")) + HTTP_REQUIRE_TRUE(map.swap(index, index + 1), "index") + g->save(); + res->setBody(warp(to_json()).to_json_string()); + set_need_refresh(); + }); +} diff --git a/module/Local_Server/server/Source_Feed_Relation.h b/module/Local_Server/server/Source_Feed_Relation.h new file mode 100644 index 0000000..4cd11b3 --- /dev/null +++ b/module/Local_Server/server/Source_Feed_Relation.h @@ -0,0 +1,95 @@ +#ifndef SOURCE_FEED_RELATION_H +#define SOURCE_FEED_RELATION_H + + +#include "Core/Serial/Serial.h" +#include "../global_include.h" + + + +struct Source_Feed_Relation { + std::string key; + std::string type; + std::atomic enable{}; + virtual void from_json(const Psc::JSON* that_json) { + Get_J(key); + Get_J(enable); + Get_J(type); + } + + [[nodiscard]] virtual Psc::JSON to_json() const { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(key) + Ret_J(enable) + Ret_J(type) + return ret; + } + virtual ~Source_Feed_Relation() = default; +}; + +struct One_to_One_Relation : Source_Feed_Relation { + std::string source_key; + std::string feed_key; + void from_json(const Psc::JSON* that_json) override { + Source_Feed_Relation::from_json(that_json); + Get_J(source_key) + Get_J(feed_key) + } + [[nodiscard]] Psc::JSON to_json() const override { + Psc::JSON ret = Source_Feed_Relation::to_json(); + Ret_J(source_key) + Ret_J(feed_key) + return ret; + } + ~One_to_One_Relation() override = default; +}; + +struct First_Source_To_All_Feed_Relation : Source_Feed_Relation { + void from_json(const Psc::JSON* that_json) override { + Source_Feed_Relation::from_json(that_json); + } + [[nodiscard]] Psc::JSON to_json() const override { + Psc::JSON ret = Source_Feed_Relation::to_json(); + return ret; + } + ~First_Source_To_All_Feed_Relation() override = default; +}; + + +inline std::shared_ptr create_source_feed_relation_from_type(const std::string& t) { + std::shared_ptr ret{}; + if (t == "One_to_One_Relation") ret = std::make_shared(); + else if (t == "First_Source_To_All_Feed_Relation") ret = std::make_shared(); + else { + std::cout << "未知的 Source_Feed_Relation type 类型! t:[" << t << "]"<< std::endl; + throw std::invalid_argument("unknown Source_Feed_Relation type: " + t); + } + return ret; +} + +struct Source_Feed_Relation_Config { + void from_json(const Psc::JSON* that_json) { + auto j_list = that_json->get("list"); + for (const Psc::JSON& li : j_list->children) { + auto p = create_source_feed_relation_from_type(li.get_string("type")); + p->from_json(&li); + map.push_back(p); + } + } + [[nodiscard]] Psc::JSON to_json() { + Psc::JSON ret = Psc::JSON::object(); + Psc::JSON j_list = Psc::JSON::array(); + for (auto& p : this->map.list()) { + j_list.append(p->to_json()); + } + ret.append(Psc::JSON("list", j_list)); + return ret; + } + Ordered_Map> map; + + static void set_need_refresh(); //通知每一个data_source 映射关系需要更新 + void server(Global* g); +}; + + +#endif diff --git a/module/Local_Server/server/WebSocket_Manager.cpp b/module/Local_Server/server/WebSocket_Manager.cpp new file mode 100644 index 0000000..c25aba8 --- /dev/null +++ b/module/Local_Server/server/WebSocket_Manager.cpp @@ -0,0 +1,115 @@ +#include "WebSocket_Manager.h" +#include "../global_include.h" +#include "Global.h" +#include +#include +#include +class DSP_Ws_Mgr : public drogon::WebSocketController { +public: + static DSP_Ws_Mgr *instance() { return inst_; } + WS_PATH_LIST_BEGIN + WS_ADD_PATH_VIA_REGEX(R"(^/ws/.*$)", drogon::Get); + WS_PATH_LIST_END + void handleNewMessage(const drogon::WebSocketConnectionPtr &, std::string &&, const drogon::WebSocketMessageType &) override; + void handleConnectionClosed(const drogon::WebSocketConnectionPtr & conn) override; + void handleNewConnection(const drogon::HttpRequestPtr & req, const drogon::WebSocketConnectionPtr & conn) override; + DSP_Ws_Mgr(); +protected: + static DSP_Ws_Mgr *inst_; +}; +DSP_Ws_Mgr *DSP_Ws_Mgr::inst_ = nullptr; +struct WebSocket_Connection { + std::string type; +}; + +WebSocket_Manager::WebSocket_Manager(std::string path) : path(std::move(path)) {} +void WebSocket_Manager::remove(const drogon::WebSocketConnectionPtr &conn) { + std::lock_guard g(mtx); + connections.erase(conn); +} +void WebSocket_Manager::add(const drogon::WebSocketConnectionPtr &conn) { + std::lock_guard g(mtx); + connections.insert(conn); +} +void WebSocket_Manager::visit( + const std::function + &func) { + std::lock_guard g(mtx); + for (const auto &coon: connections) { + func(coon); + } +} +void WebSocket_Manager::push_data(const char *buf, uint64_t len) { + std::lock_guard g(mtx); + for (const auto &conn: connections) { + conn->send(buf, len, drogon::WebSocketMessageType::Binary); + } +} +size_t WebSocket_Manager::get_websocket_connect_num() { + std::lock_guard g(mtx); + return connections.size(); +} + +std::shared_ptr Ws_Mgr::register_ws(std::string path) { + auto it = map.find(path); + if (it != map.end()) { + Psc::fail_fast_core_dump("register_ws:" + VAR_STR_1(path) + "重复创建!"); + return it->second; + } + std::shared_ptr cur = std::make_shared(path); + map[path] = cur; + return cur; +} + +std::shared_ptr Ws_Mgr::get_ws(std::string path) { + auto it = map.find(path); + if (it != map.end()) { + return it->second; + } + return nullptr; +} + + +void DSP_Ws_Mgr::handleNewMessage(const drogon::WebSocketConnectionPtr &, + std::string &&, + const drogon::WebSocketMessageType &) { + +} +void DSP_Ws_Mgr::handleConnectionClosed( +const drogon::WebSocketConnectionPtr &conn) { + auto &s = conn->getContextRef(); + auto rp = s.type; + auto ws = Ws_Mgr::instance()->get_ws(rp); + //LOG_DEBUG + std::ostringstream oss; + oss + << "已有连接数:" << ws->get_websocket_connect_num() << " " + << "关闭websocket连接:" << rp << "\n" + << "local_addr:" << inet_address_to_string(conn->localAddr()) << "\n" + << "peer_addr:" << inet_address_to_string(conn->peerAddr()) << "\n"; + dsp_logger->c_debug("websocket", {},oss.str()); + ws->remove(conn); +} +void DSP_Ws_Mgr::handleNewConnection( + const drogon::HttpRequestPtr &req, + const drogon::WebSocketConnectionPtr &conn) { + auto rpt = req->path(); + auto rp = rpt.substr(std::string("/ws").size()); + auto ws = Ws_Mgr::instance()->get_ws(rp); + std::ostringstream oss; + oss + << "已有连接数:" << ws->get_websocket_connect_num() << " " + << "新建websocket连接:" << rp << " " + << "local_addr:" << inet_address_to_string(conn->localAddr()) << " " + << "peer_addr:" << inet_address_to_string(conn->peerAddr()) << "\n"; + + dsp_logger->c_debug("websocket", {},oss.str()); + + WebSocket_Connection s; + s.type = rp; + conn->setContext(std::make_shared(std::move(s))); + ws->add(conn); +} +DSP_Ws_Mgr::DSP_Ws_Mgr() { + inst_ = this; +} \ No newline at end of file diff --git a/module/Local_Server/server/WebSocket_Manager.h b/module/Local_Server/server/WebSocket_Manager.h new file mode 100644 index 0000000..62f7ab4 --- /dev/null +++ b/module/Local_Server/server/WebSocket_Manager.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Core/Base/global_include.h" +#include "Core/socket/ByteOrder.h" + +#include +#include + + + +class WebSocket_Manager { +public: + explicit WebSocket_Manager(std::string path); + ~WebSocket_Manager() = default; + void remove(const drogon::WebSocketConnectionPtr &conn); + void add(const drogon::WebSocketConnectionPtr &conn); + void visit(const std::function &func); + void push_data(const char *buf, uint64_t len); + size_t get_websocket_connect_num(); + std::string path; +protected: + std::mutex mtx; + std::unordered_set connections; +}; + + +class Ws_Mgr : public Psc::Singleton { +public: + std::shared_ptr register_ws(std::string path); + std::shared_ptr get_ws(std::string path); + std::map> map; +}; + + + + diff --git a/module/Local_Server/server/libarchive.cpp b/module/Local_Server/server/libarchive.cpp new file mode 100644 index 0000000..4af92c8 --- /dev/null +++ b/module/Local_Server/server/libarchive.cpp @@ -0,0 +1,678 @@ +#include "libarchive.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace { + +constexpr std::size_t kArchiveBlockSize = 64 * 1024; + +[[noreturn]] void throw_invalid_archive(const std::string& reason) { + throw Invalid_Archive_Error("invalid upgrade archive: " + reason); +} + +std::string archive_error_text(archive* a, const std::string& stage) { + const char* err = archive_error_string(a); + if (err == nullptr || *err == '\0') { + return stage; + } + return stage + ": " + err; +} + +void require_archive_ok(archive* a, int code, const std::string& stage) { + if (code != ARCHIVE_OK) { + throw std::runtime_error(archive_error_text(a, stage)); + } +} + +std::string path_to_utf8(const fs::path& path) { + const auto u8 = path.generic_u8string(); + return std::string(u8.begin(), u8.end()); +} + +bool path_is_within(const fs::path& base, const fs::path& target) { + const fs::path canonical_base = fs::weakly_canonical(base); + const fs::path canonical_target = fs::weakly_canonical(target); + + auto base_it = canonical_base.begin(); + auto target_it = canonical_target.begin(); + for (; base_it != canonical_base.end(); ++base_it, ++target_it) { + if (target_it == canonical_target.end() || *base_it != *target_it) { + return false; + } + } + return true; +} + +std::string normalize_archive_path(const std::string& raw, bool allow_empty = false) { + if (raw.empty()) { + if (allow_empty) return {}; + throw_invalid_archive("empty entry path"); + } + if (raw.front() == '/' || raw.find('\\') != std::string::npos || raw.find(':') != std::string::npos) { + throw_invalid_archive("unsafe entry path: " + raw); + } + + std::string normalized; + std::size_t start = 0; + while (start < raw.size()) { + const std::size_t slash = raw.find('/', start); + const std::size_t end = slash == std::string::npos ? raw.size() : slash; + const std::string component = raw.substr(start, end - start); + if (component.empty() || component == "." || component == "..") { + throw_invalid_archive("unsafe entry path: " + raw); + } + if (!normalized.empty()) normalized.push_back('/'); + normalized += component; + if (slash == std::string::npos) break; + start = slash + 1; + if (start == raw.size()) break; + } + return normalized; +} + +std::string entry_path(archive_entry* entry) { + const char* name = archive_entry_pathname_utf8(entry); + if (name == nullptr) { + name = archive_entry_pathname(entry); + } + if (name == nullptr) { + throw_invalid_archive("entry path is unavailable"); + } + return normalize_archive_path(name); +} + +bool is_directory_entry(archive_entry* entry) { + return archive_entry_filetype(entry) == AE_IFDIR; +} + +bool is_regular_entry(archive_entry* entry) { + const auto type = archive_entry_filetype(entry); + return type == AE_IFREG || type == 0; +} + +void reject_unsupported_entry_type(archive_entry* entry, const std::string& path) { + const auto type = archive_entry_filetype(entry); + if (type == AE_IFLNK) { + throw_invalid_archive("symbolic link is not allowed: " + path); + } + if (type != AE_IFDIR && type != AE_IFREG && type != 0) { + throw_invalid_archive("special file is not allowed: " + path); + } +} + +void setup_archive_reader(archive* a) { + require_archive_ok(a, archive_read_support_filter_all(a), "archive_read_support_filter_all failed"); + require_archive_ok(a, archive_read_support_format_all(a), "archive_read_support_format_all failed"); +} + +void consume_entry_data_for_validation( + archive* a, + const std::string& path, + const Archive_Layout_Rules& rules, + std::uint64_t& total_uncompressed_size +) { + std::uint64_t entry_size = 0; + const void* block = nullptr; + std::size_t block_size = 0; + la_int64_t offset = 0; + + while (true) { + const int result = archive_read_data_block(a, &block, &block_size, &offset); + if (result == ARCHIVE_EOF) { + break; + } + if (result != ARCHIVE_OK) { + throw_invalid_archive(archive_error_text(a, "cannot read entry: " + path)); + } + + if (block_size > rules.max_entry_uncompressed_size + || entry_size > rules.max_entry_uncompressed_size - block_size) { + throw_invalid_archive("entry size exceeds limit: " + path); + } + if (block_size > rules.max_total_uncompressed_size + || total_uncompressed_size > rules.max_total_uncompressed_size - block_size) { + throw_invalid_archive("uncompressed size exceeds limit"); + } + + entry_size += block_size; + total_uncompressed_size += block_size; + } +} + +void consume_entry_data_for_validation( + archive* a, + archive_entry* entry, + const std::string& path, + const Archive_Layout_Rules& rules, + std::uint64_t& total_uncompressed_size +) { + if (archive_entry_size_is_set(entry)) { + const auto expected_size = archive_entry_size(entry); + if (expected_size < 0) { + throw_invalid_archive("negative entry size: " + path); + } + const auto size = static_cast(expected_size); + if (size > rules.max_entry_uncompressed_size || size > rules.max_total_uncompressed_size) { + throw_invalid_archive("entry size exceeds limit: " + path); + } + } + + consume_entry_data_for_validation(a, path, rules, total_uncompressed_size); +} + +std::unordered_set make_allowed_entries( + const std::vector& required, + const std::vector& optional +) { + std::unordered_set result; + auto add = [&result](const std::vector& entries) { + for (const auto& entry : entries) { + if (entry.empty() || entry.find('/') != std::string::npos || entry.find('\\') != std::string::npos) { + throw_invalid_archive("invalid layout rule entry: " + entry); + } + result.insert(entry); + } + }; + add(required); + add(optional); + return result; +} + +struct Validated_Archive_Entry { + std::string path; + bool is_directory = false; +}; + +struct Archive_Read_Deleter { + void operator()(archive* a) const noexcept { + if (a != nullptr) { + archive_read_free(a); + } + } +}; + +struct Archive_Write_Deleter { + void operator()(archive* a) const noexcept { + if (a != nullptr) { + archive_write_free(a); + } + } +}; + +using Read_Archive_Ptr = std::unique_ptr; +using Write_Archive_Ptr = std::unique_ptr; + +Read_Archive_Ptr make_archive_reader() { + Read_Archive_Ptr reader(archive_read_new()); + if (!reader) { + throw std::runtime_error("archive_read_new failed"); + } + setup_archive_reader(reader.get()); + return reader; +} + +bool is_safe_archive_name_token(const std::string& value) { + if (value.empty()) { + return false; + } + return std::ranges::all_of(value, [](unsigned char ch) { + return std::isalnum(ch) != 0 || ch == '_' || ch == '-' || ch == '.'; + }); +} + +void validate_archive_write_options(const Archive_Write_Options& options) { + if (!is_safe_archive_name_token(options.format)) { + throw std::invalid_argument("invalid archive format: " + options.format); + } + if (!options.filter.empty() + && options.filter != "none" + && !is_safe_archive_name_token(options.filter)) { + throw std::invalid_argument("invalid archive filter: " + options.filter); + } + if (!is_safe_archive_name_token(options.extension) || options.extension.find('/') != std::string::npos) { + throw std::invalid_argument("invalid archive extension: " + options.extension); + } +} + +void configure_archive_writer(archive* writer, const Archive_Write_Options& options) { + validate_archive_write_options(options); + + if (!options.filter.empty() && options.filter != "none") { + require_archive_ok( + writer, + archive_write_add_filter_by_name(writer, options.filter.c_str()), + "archive_write_add_filter_by_name failed: " + options.filter + ); + } + + require_archive_ok( + writer, + archive_write_set_format_by_name(writer, options.format.c_str()), + "archive_write_set_format_by_name failed: " + options.format + ); + + if (options.format == "zip") { + require_archive_ok( + writer, + archive_write_zip_set_compression_deflate(writer), + "archive_write_zip_set_compression_deflate failed" + ); + } +} + +Write_Archive_Ptr make_archive_writer(const Archive_Write_Options& options) { + Write_Archive_Ptr writer(archive_write_new()); + if (!writer) { + throw std::runtime_error("archive_write_new failed"); + } + configure_archive_writer(writer.get(), options); + return writer; +} + +void extract_archive(archive* a, const fs::path& out_dir) { + fs::create_directories(out_dir); + const fs::path base = fs::weakly_canonical(out_dir); + + archive_entry* entry = nullptr; + while (true) { + const int result = archive_read_next_header(a, &entry); + if (result == ARCHIVE_EOF) { + break; + } + if (result != ARCHIVE_OK) { + throw std::runtime_error(archive_error_text(a, "archive_read_next_header failed")); + } + + const std::string normalized = entry_path(entry); + reject_unsupported_entry_type(entry, normalized); + + const fs::path out_path = (base / fs::path(normalized)).lexically_normal(); + if (!path_is_within(base, out_path)) { + throw std::runtime_error("archive-slip detected: " + normalized); + } + + if (is_directory_entry(entry)) { + fs::create_directories(out_path); + continue; + } + + if (!is_regular_entry(entry)) { + throw std::runtime_error("unsupported archive entry type: " + normalized); + } + + fs::create_directories(out_path.parent_path()); + std::ofstream out(out_path, std::ios::binary); + if (!out) { + throw std::runtime_error("open output failed: " + path_to_utf8(out_path)); + } + + const void* block = nullptr; + std::size_t block_size = 0; + la_int64_t offset = 0; + while (true) { + const int read_result = archive_read_data_block(a, &block, &block_size, &offset); + if (read_result == ARCHIVE_EOF) { + break; + } + if (read_result != ARCHIVE_OK) { + throw std::runtime_error(archive_error_text(a, "archive_read_data_block failed")); + } + out.write(static_cast(block), static_cast(block_size)); + if (!out) { + throw std::runtime_error("write output failed: " + path_to_utf8(out_path)); + } + } + } +} + +struct Ignore_Rules { + std::vector directories; + std::unordered_set files; + std::unordered_set extensions; +}; + +Ignore_Rules parse_ignore_rules(const std::vector& ignore_patterns) { + Ignore_Rules rules; + for (const auto& pattern : ignore_patterns) { + if (pattern.empty()) continue; + if (pattern.ends_with('/')) { + rules.directories.emplace_back(fs::path(pattern.substr(0, pattern.size() - 1)).lexically_normal()); + } + else if (pattern[0] == '.') { + rules.extensions.insert(pattern); + } + else { + rules.files.insert(pattern); + } + } + return rules; +} + +bool should_skip_directory(const fs::path& rel_path, const Ignore_Rules& rules) { + for (const auto& ignored : rules.directories) { + const auto rel_native = rel_path.native(); + const auto ignored_native = ignored.native(); + if (rel_path == ignored || rel_native.starts_with(ignored_native + fs::path::preferred_separator)) { + return true; + } + } + return false; +} + +void add_file_to_archive(archive* writer, const fs::path& base, const fs::path& abs_path, const fs::path& rel_path) { + const std::uintmax_t file_size = fs::file_size(abs_path); + const std::string archive_name = path_to_utf8(rel_path); + if (archive_name.empty() + || archive_name == ".." + || archive_name.starts_with("../") + || archive_name.find("/../") != std::string::npos + || archive_name.ends_with("/..")) { + throw std::runtime_error("invalid path in archive: " + archive_name); + } + + std::unique_ptr entry(archive_entry_new(), archive_entry_free); + if (!entry) { + throw std::runtime_error("archive_entry_new failed"); + } + archive_entry_set_pathname_utf8(entry.get(), archive_name.c_str()); + archive_entry_set_filetype(entry.get(), AE_IFREG); + archive_entry_set_perm(entry.get(), 0644); + archive_entry_set_size(entry.get(), static_cast(file_size)); + + const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + archive_entry_set_mtime(entry.get(), now, 0); + + require_archive_ok(writer, archive_write_header(writer, entry.get()), "archive_write_header failed"); + + std::ifstream in(abs_path, std::ios::binary); + if (!in) { + throw std::runtime_error("failed to open input file: " + path_to_utf8(abs_path)); + } + + char buffer[kArchiveBlockSize]; + while (in) { + in.read(buffer, sizeof(buffer)); + const auto count = in.gcount(); + if (count <= 0) { + break; + } + const la_ssize_t written = archive_write_data(writer, buffer, static_cast(count)); + if (written < 0 || written != count) { + throw std::runtime_error(archive_error_text(writer, "archive_write_data failed")); + } + } + if (!in.eof()) { + throw std::runtime_error("failed to read input file: " + path_to_utf8(abs_path)); + } + + require_archive_ok(writer, archive_write_finish_entry(writer), "archive_write_finish_entry failed"); + (void)base; +} + +void write_directory_to_archive( + archive* writer, + const fs::path& dir_path, + const std::vector& ignore_patterns +) { + if (!fs::exists(dir_path)) { + throw std::runtime_error("source path does not exist: " + path_to_utf8(dir_path)); + } + if (!fs::is_directory(dir_path)) { + throw std::runtime_error("source path exists but is not a directory: " + path_to_utf8(dir_path)); + } + + const fs::path base = fs::weakly_canonical(dir_path); + const Ignore_Rules ignore_rules = parse_ignore_rules(ignore_patterns); + + fs::recursive_directory_iterator it(base); + const fs::recursive_directory_iterator end; + for (; it != end; ++it) { + const fs::path abs_path = it->path(); + const fs::path rel_path = fs::relative(abs_path, base); + + if (it->is_directory()) { + if (should_skip_directory(rel_path, ignore_rules)) { + it.disable_recursion_pending(); + } + continue; + } + + if (!it->is_regular_file()) { + continue; + } + + const std::string filename = abs_path.filename().string(); + const std::string extension = abs_path.extension().string(); + if (ignore_rules.files.contains(filename) || (!extension.empty() && ignore_rules.extensions.contains(extension))) { + continue; + } + + add_file_to_archive(writer, base, abs_path, rel_path); + } +} + +la_ssize_t archive_string_write_callback(archive*, void* client_data, const void* buffer, std::size_t length) { + auto* out = static_cast(client_data); + out->append(static_cast(buffer), length); + return static_cast(length); +} + +int archive_noop_open_close_callback(archive*, void*) { + return ARCHIVE_OK; +} + +void validate_archive_layout_from_memory_or_throw( + const char* data, + std::size_t size, + const Archive_Layout_Rules& rules +) { + if (data == nullptr || size == 0) { + throw_invalid_archive("empty archive"); + } + if (size > rules.max_archive_size) { + throw_invalid_archive("archive size exceeds limit"); + } + + auto reader = make_archive_reader(); + require_archive_ok( + reader.get(), + archive_read_open_memory(reader.get(), data, size), + "archive_read_open_memory failed" + ); + + std::vector entries; + std::unordered_set unique_paths; + std::uint64_t total_uncompressed_size = 0; + std::size_t entry_count = 0; + + archive_entry* entry = nullptr; + while (true) { + const int result = archive_read_next_header(reader.get(), &entry); + if (result == ARCHIVE_EOF) { + break; + } + if (result != ARCHIVE_OK) { + throw_invalid_archive(archive_error_text(reader.get(), "cannot read archive header")); + } + + ++entry_count; + if (entry_count > rules.max_entry_count) { + throw_invalid_archive("entry count exceeds limit"); + } + + const std::string path = entry_path(entry); + if (!unique_paths.insert(path).second) { + throw_invalid_archive("duplicate entry path: " + path); + } + + if (archive_entry_is_encrypted(entry)) { + throw_invalid_archive("encrypted entry is not allowed: " + path); + } + + reject_unsupported_entry_type(entry, path); + + const bool directory = is_directory_entry(entry); + if (!directory) { + if (!is_regular_entry(entry)) { + throw_invalid_archive("special file is not allowed: " + path); + } + consume_entry_data_for_validation(reader.get(), entry, path, rules, total_uncompressed_size); + } + + entries.push_back({path, directory}); + } + + for (const auto& rule : rules.directories) { + const std::string rule_directory = normalize_archive_path(rule.directory, true); + const auto allowed_files = make_allowed_entries(rule.required_files, rule.optional_files); + const auto allowed_directories = make_allowed_entries(rule.required_directories, rule.optional_directories); + std::unordered_map direct_children; + + for (const auto& entry : entries) { + std::string relative; + if (rule_directory.empty()) { + relative = entry.path; + } + else { + const std::string prefix = rule_directory + "/"; + if (!entry.path.starts_with(prefix)) continue; + relative = entry.path.substr(prefix.size()); + } + + const std::size_t slash = relative.find('/'); + const std::string child = relative.substr(0, slash); + const bool is_directory = slash != std::string::npos || entry.is_directory; + const auto [it, inserted] = direct_children.emplace(child, is_directory); + if (!inserted && it->second != is_directory) { + throw_invalid_archive("entry is both file and directory: " + child); + } + } + + for (const auto& [child, is_directory] : direct_children) { + const auto& allowed = is_directory ? allowed_directories : allowed_files; + if (!allowed.contains(child)) { + throw_invalid_archive("unexpected entry under " + rule_directory + ": " + child); + } + } + for (const auto& required : rule.required_files) { + const auto it = direct_children.find(required); + if (it == direct_children.end() || it->second) { + throw_invalid_archive("missing required file under " + rule_directory + ": " + required); + } + } + for (const auto& required : rule.required_directories) { + const auto it = direct_children.find(required); + if (it == direct_children.end() || !it->second) { + throw_invalid_archive("missing required directory under " + rule_directory + ": " + required); + } + } + } +} + +} // namespace + +Psc::expected validate_archive_layout_from_memory( + const char* data, + std::size_t size, + const Archive_Layout_Rules& rules +) { + try { + validate_archive_layout_from_memory_or_throw(data, size, rules); + return {}; + } + catch (const Invalid_Archive_Error& e) { + return Psc::unexpected(e.what()); + } +} + +void unarchive_file(const fs::path& zipPath, const fs::path& outDir) { + auto reader = make_archive_reader(); +#ifdef _WIN32 + require_archive_ok( + reader.get(), + archive_read_open_filename_w(reader.get(), zipPath.wstring().c_str(), kArchiveBlockSize), + "archive_read_open_filename_w failed" + ); +#else + require_archive_ok( + reader.get(), + archive_read_open_filename(reader.get(), zipPath.c_str(), kArchiveBlockSize), + "archive_read_open_filename failed" + ); +#endif + extract_archive(reader.get(), outDir); +} + +void unarchive_from_memory(const char* data, size_t size, const fs::path& outDir) { + if (data == nullptr || size == 0) { + throw std::runtime_error("empty archive"); + } + + auto reader = make_archive_reader(); + require_archive_ok( + reader.get(), + archive_read_open_memory(reader.get(), data, size), + "archive_read_open_memory failed" + ); + extract_archive(reader.get(), outDir); +} + +void archive_directory( + const fs::path& dirPath, + const fs::path& archive_path, + const Archive_Write_Options& options +) { + auto writer = make_archive_writer(options); +#ifdef _WIN32 + require_archive_ok( + writer.get(), + archive_write_open_filename_w(writer.get(), archive_path.wstring().c_str()), + "archive_write_open_filename_w failed" + ); +#else + require_archive_ok( + writer.get(), + archive_write_open_filename(writer.get(), archive_path.c_str()), + "archive_write_open_filename failed" + ); +#endif + write_directory_to_archive(writer.get(), dirPath, {}); + require_archive_ok(writer.get(), archive_write_close(writer.get()), "archive_write_close failed"); +} + +std::string archive_directory_to_memory( + const fs::path& dirPath, + const std::vector& ignorePatterns, + const Archive_Write_Options& options +) { + std::string out; + auto writer = make_archive_writer(options); + require_archive_ok( + writer.get(), + archive_write_open( + writer.get(), + &out, + archive_noop_open_close_callback, + archive_string_write_callback, + archive_noop_open_close_callback + ), + "archive_write_open failed" + ); + write_directory_to_archive(writer.get(), dirPath, ignorePatterns); + require_archive_ok(writer.get(), archive_write_close(writer.get()), "archive_write_close failed"); + return out; +} diff --git a/module/Local_Server/server/libarchive.h b/module/Local_Server/server/libarchive.h new file mode 100644 index 0000000..f775c15 --- /dev/null +++ b/module/Local_Server/server/libarchive.h @@ -0,0 +1,60 @@ +#ifndef PLANTSUNCAT_ZIP_H +#define PLANTSUNCAT_ZIP_H +#include "Core/Base/global_include.h" +#include +#include +#include +#include +#include +#include + +class Invalid_Archive_Error final : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; + +struct Archive_Directory_Layout_Rule { + std::string directory; + std::vector required_files; + std::vector required_directories; + std::vector optional_files; + std::vector optional_directories; +}; + +struct Archive_Layout_Rules { + std::vector directories; + std::size_t max_archive_size = 128 * 1024 * 1024; + std::size_t max_entry_count = 16 * 1024; + std::uint64_t max_entry_uncompressed_size = 256ULL * 1024 * 1024; + std::uint64_t max_total_uncompressed_size = 512ULL * 1024 * 1024; +}; + +struct Archive_Write_Options { + std::string format = "zip"; + std::string filter; + std::string extension = "zip"; +}; + +Psc::expected validate_archive_layout_from_memory( + const char* data, + std::size_t size, + const Archive_Layout_Rules& rules +); + +void unarchive_file(const std::filesystem::path& zipPath, const std::filesystem::path& outDir); +void unarchive_from_memory(const char* data, size_t size, const std::filesystem::path& outDir); +void archive_directory( + const std::filesystem::path& dirPath, + const std::filesystem::path& archive_path, + const Archive_Write_Options& options = {} +); +// std::string zip_directory_to_memory(const std::filesystem::path& dirPath, +// const std::vector& ignoreDirs); + +std::string +archive_directory_to_memory( + const std::filesystem::path& dirPath, + const std::vector& ignorePatterns, + const Archive_Write_Options& options = {} +); +#endif diff --git a/module/Local_Server/server/server.cpp b/module/Local_Server/server/server.cpp new file mode 100644 index 0000000..f202964 --- /dev/null +++ b/module/Local_Server/server/server.cpp @@ -0,0 +1,679 @@ + +#include "Global.h" +#include "Performance_Monitor.h" + +#include +#include +#include + +namespace { + +constexpr std::size_t kMaxLogRequestBodySize = 8 * 1024; + +void Log_Request_Body_If_Small(const drogon::HttpRequestPtr& request) { + const auto& body = request->getBody(); + if (body.empty()) { + return; + } + + if (body.size() <= kMaxLogRequestBodySize) { + LOG_INFO + << "[request body] " + << "path=" << request->path() + << ", size=" << body.size() + << ", body=" << std::string(body.data(), body.size()); + } + else { + LOG_INFO + << "[request body skipped] " + << "path=" << request->path() + << ", size=" << body.size() + << ", max=" << kMaxLogRequestBodySize; + } +} + +bool Is_Invalid_Http_Param(const std::exception& e) { + return dynamic_cast(&e) != nullptr + || dynamic_cast(&e) != nullptr + || dynamic_cast(&e) != nullptr; +} + +bool Is_Safe_Version_Directory_Name(std::string_view name) { + if (name.empty() || name == "." || name == "..") { + return false; + } + return std::ranges::all_of(name, [](unsigned char ch) { + return std::isalnum(ch) != 0 || ch == '.' || ch == '_' || ch == '-'; + }); +} + +bool Is_Safe_Archive_Extension(std::string_view extension) { + if (extension.empty() || extension.front() == '.' || extension.back() == '.') { + return false; + } + return std::ranges::all_of(extension, [](unsigned char ch) { + return std::isalnum(ch) != 0 || ch == '.' || ch == '_' || ch == '-'; + }); +} + +std::string Normalize_Archive_Extension(std::string extension, std::string fallback = "zip") { + while (!extension.empty() && extension.front() == '.') { + extension.erase(extension.begin()); + } + if (extension.empty()) { + extension = std::move(fallback); + } + if (!Is_Safe_Archive_Extension(extension)) { + throw std::invalid_argument("invalid archive extension: " + extension); + } + return extension; +} + +std::optional Try_Get_String_Field(const JSON& object, const std::string& key) { + const JSON* value = object.get(key); + if (value == nullptr) { + return std::nullopt; + } + if (value->valueType != Psc::String) { + throw std::invalid_argument(key + " must be a string"); + } + return value->val; +} + +struct Archive_Export_Request { + std::string version; + Archive_Write_Options archive; +}; + +Archive_Export_Request Parse_Archive_Export_Request( + const drogon::HttpRequestPtr& req, + std::string default_version +) { + Archive_Export_Request result; + result.version = std::move(default_version); + + const auto& body = req->getBody(); + if (body.empty()) { + return result; + } + + auto parsed = Psc::try_parse_json(body.data()); + if (!parsed.has_value()) { + throw_invalid_http_param("request body is not valid JSON"); + } + + const JSON& params = parsed.value(); + if (auto version = Try_Get_String_Field(params, "version")) { + if (!version->empty()) { + result.version = *version; + } + } + if (!Is_Safe_Version_Directory_Name(result.version)) { + throw std::invalid_argument("invalid version name: " + result.version); + } + + if (auto format = Try_Get_String_Field(params, "format")) { + if (!format->empty()) { + result.archive.format = *format; + } + } + if (auto filter = Try_Get_String_Field(params, "filter")) { + result.archive.filter = *filter; + } + if (auto extension = Try_Get_String_Field(params, "extension")) { + if (!extension->empty()) { + result.archive.extension = Normalize_Archive_Extension(*extension); + } + } + else { + result.archive.extension = Normalize_Archive_Extension(result.archive.extension); + } + + return result; +} + +const Archive_Layout_Rules& Upgrade_Zip_Layout_Rules() { + static const Archive_Layout_Rules rules = [] { + Archive_Layout_Rules value; + value.directories = { + { + "", + {"adsb_cfg.ini", "config.json", "ecap_server", "lib9002config.json"}, + {"lib9002", "wwwroot"}, + {}, + {} + }, + { + "lib9002", + {"lib9002.so", "testserver"}, + {"env"}, + {}, + {} + } + }; + return value; + }(); + return rules; +} + +} // namespace + +void setupCors(); +void Global::init_web_server() { + register_http_performance_monitor( + http_monitor_config.access_log, + http_monitor_config.slow_request_threshold_ms); + setupCors(); + static auto &web_server = web_server_config; + Web_Server &svr = this->svr; + auto &da = drogon::app(); + auto return_file = [](const std::string &path) { + auto res = drogon::HttpResponse::newHttpResponse(); + if (std::filesystem::exists(path)) { + std::string content; + httplib::detail::read_file(path, content); + auto content_type = httplib::detail::find_content_type(path, {}, "application/octet-stream"); + res->setContentTypeString(content_type); + res->setBody(content); + // res.set_file_content(path); + } else { + res->setStatusCode(drogon::k404NotFound); + JSON ret = JSON::object(); + ret.append({"LOG_POS", LOG_POS}); + ret.append({"not_found_path", path}); + res->setContentTypeCode(drogon::CT_TEXT_PLAIN); + res->setBody(ret.to_json_string()); + } + return res; + }; + auto add_path = [return_file](const std::string &path) { + drogon::app().registerHandlerViaRegex( + path, + [return_file](const drogon::HttpRequestPtr &req, + std::function &&callback) { + callback(return_file(web_server.get_true_webapp() + "/index.html")); + }, + {drogon::Get}); + }; + + + drogon::app().registerHandlerViaRegex( + R"(^/tiles/.*$)", + [return_file](const drogon::HttpRequestPtr &req, + std::function &&callback) { + auto path = web_server.get_true_tiles() + req->getPath().substr(6); + callback(return_file(path)); + }, + {drogon::Get}); + + + drogon::app().registerHandlerViaRegex( + "/", + [return_file](const drogon::HttpRequestPtr &req, + std::function &&callback) { + callback(return_file(web_server.get_true_webapp() + "/index.html")); + }, + {drogon::Get}); + + + add_path(R"(^/settings)"); + add_path(R"(^/map)"); + add_path(R"(^/dsp)"); + add_path(R"(^/aircraftlist)"); + drogon::app().registerHandlerViaRegex( + R"(^/ui(/.*)?$)", [return_file](const drogon::HttpRequestPtr &req, + std::function &&callback) { + std::cout << req->path() << std::endl; + auto res = drogon::HttpResponse::newHttpResponse(); + auto path = web_server.get_true_webapp(); + auto rp = "/" + req->path().substr(3); + if (rp == "/") { + path += "/index.html"; + } else { + path += rp; + } + callback(return_file(path)); + }); + + State_Report::instance(); + svr.Get(R"(/get_debug_info)", [](HTTP_Param) { + JSON arr = JSON::array(); + auto &sp = Global::instance()->mode_acs.data_feed_config.pool_; + arr.append(JSON::object({{"remain_size", sp.remain_size()}, {"free_size", sp.free_size()}})); + res->setBody(arr.to_json_string("", "\n", "")); + }); + + + mlat.server(this); + database_server(this); + external_resources_manager.server(this); + device_config.server(this); + mode_acs.server(this); + dsp_config.server(this); + + + // s.w.init(); + + drogon::app().registerHandler( + api + "export_logs", + [](const drogon::HttpRequestPtr& req, + std::function&& cb) { + try { + auto export_request = Parse_Archive_Export_Request(req, Global::instance()->version); + const std::string& name = export_request.version; + auto version_dir = std::filesystem::path(get_exe_dir() + "/.."); + std::string data = archive_directory_to_memory(version_dir /name/"logs", + {}, + export_request.archive + ); + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setBody(data); + resp->setContentTypeString("application/octet-stream"); + resp->addHeader( + "Content-Disposition", + "attachment; filename*=UTF-8''" + name + "_logs." + export_request.archive.extension + ); + resp->addHeader( + "ok", "true" + ); + cb(resp); + } + + + + catch (const std::exception& e) { + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->addHeader( + "ok", "false" + ); + Psc::JSON json = JSON::object({ + {"message", Psc::platform_2_utf8(e.what()) } + }); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + resp->setBody(json.to_json_string()); + cb(resp); + } + } + ); + + drogon::app().registerHandler( + api + "export_version", + [](const drogon::HttpRequestPtr& req, + std::function&& cb) { + try { + auto export_request = Parse_Archive_Export_Request(req, Global::instance()->version); + const std::string& name = export_request.version; + auto version_dir = std::filesystem::path(get_exe_dir() + "/.."); + + std::cout<< "准备压缩:" << std::endl; + std::string data = archive_directory_to_memory(version_dir /name, + { + "logs/", + "uploads/", + "drogon_uploads/", + "backups/", // ghw的什么文件路径 + "stack_trace.txt", + "pt.log", + "ecap_server.log", + "Core", + "core", + name + ".zip", + name + "." + export_request.archive.extension + }, + export_request.archive + ); + std::cout<< "压缩成功:" << data.size() << std::endl; + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setBody(data); + resp->setContentTypeString("application/octet-stream"); + resp->addHeader( + "Content-Disposition", + "attachment; filename*=UTF-8''" + name + "." + export_request.archive.extension + ); + resp->addHeader( + "ok", "true" + ); + cb(resp); + } + catch (const std::exception& e) { + auto message = Psc::platform_2_utf8(e.what()); + std::cout << "导出异常触发:" << message << " " << LOG_POS << std::endl; + auto resp = drogon::HttpResponse::newHttpResponse(); + + Psc::JSON json = JSON::object({ + {"message", message} + }); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + resp->setBody(json.to_json_string()); + resp->addHeader( + "ok", "false" + ); + cb(resp); + } + } + ); + + + + + drogon::app().registerHandler( + api + "restart_device",[] (const drogon::HttpRequestPtr& req, + std::function&& cb) { + + #ifdef __linux__ + std::string root_dir = Psc::get_exe_dir() + "/.."; + + std::string log_file = fmt::format("{}/restart_device.log", root_dir); + std::string cmd = fmt::format( + R"(systemd-run --scope --slice=system.slice sudo bash "{}/restart_device.bash" > {} 2>&1 &)", + root_dir, + log_file + ); + std::cout << "Generated Command: " << cmd << std::endl; + std::system(cmd.c_str()); + #endif + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + resp->setBody(R"({"ok":true})"); + cb(resp); + + } + ); + + + drogon::app().registerHandler( + api + "import_version", + [] + (const drogon::HttpRequestPtr& req, + std::function&& cb) + { + try { + // ---------- 参数校验 ---------- + auto dir = req->getHeader("dir"); + if (!Is_Safe_Version_Directory_Name(dir)) { + throw std::invalid_argument("invalid or missing header: dir"); + } + + const auto& body = req->getBody(); + auto archive_layout_result = validate_archive_layout_from_memory( + body.data(), + body.size(), + Upgrade_Zip_Layout_Rules() + ); + if (!archive_layout_result) { + throw Invalid_Archive_Error(archive_layout_result.error()); + } + + auto version_dir = std::filesystem::path(get_exe_dir() + "/.."); + // ---------- 路径准备 ---------- + auto targetDir = version_dir / dir; + + std::cout << "========dir====" << dir << std::endl; + std::cout << "========version_dir====" << version_dir << std::endl; + std::cout << "========targetDir====" << targetDir << std::endl; + + std::filesystem::create_directories(targetDir); + + const auto archive_extension = Normalize_Archive_Extension(req->getHeader("archive-extension")); + auto archivePath = targetDir / (dir + "." + archive_extension); + + // ---------- 写 zip ---------- + std::ofstream ofs(archivePath, std::ios::binary); + if (!ofs) { + throw std::runtime_error("open archive failed: " + archivePath.string()); + } + + ofs.write(body.data(), body.size()); + if (!ofs) { + throw std::runtime_error("write archive failed: " + archivePath.string()); + } + ofs.close(); + if (!ofs) { + throw std::runtime_error("close archive failed: " + archivePath.string()); + } + + + + + #ifdef _WIN32 + std::string root_dir = version_dir.string(); + std::string old_version = Global::instance()->version; // Global version info + std::string new_version = dir; + std::string log_file = fmt::format("{}/upgrade_{}_to_{}.log", root_dir, old_version, new_version); + std::string cmd = fmt::format( + R"(powershell -NoProfile -ExecutionPolicy Bypass -Command "& {{ . '{}/upgrade.legacy' '{}' '{}' > '{}' 2>&1 }}" )", + root_dir, + old_version, + new_version, + log_file + ); + #else + std::string root_dir = version_dir.string(); + std::string old_version = Global::instance()->version; // Global version info + std::string new_version = dir; + std::string log_file = fmt::format("{}/upgrade_{}_to_{}.log",root_dir, old_version, new_version); + std::string cmd = fmt::format( + R"(systemd-run --scope --slice=system.slice sudo bash "{}/upgrade.bash" "{}" "{}" > {} 2>&1 &)", + root_dir, + old_version, + new_version, + log_file + ); + #endif + std::cout << "Generated Command: " << cmd << std::endl; + std::system(cmd.c_str()); + // ---------- 成功响应 ---------- + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + resp->setBody(R"({"ok":true})"); + cb(resp); + } + catch (const std::exception& e) { + auto resp = drogon::HttpResponse::newHttpResponse(); + const bool invalid_input = + dynamic_cast(&e) != nullptr + || dynamic_cast(&e) != nullptr; + resp->setStatusCode( + invalid_input + ? drogon::k400BadRequest + : drogon::k500InternalServerError + ); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + auto reason = Psc::platform_2_utf8(e.what()); + std::cout << "异常" << reason << std::endl; + Psc::JSON json = JSON::object({ + {"ok", false}, + {"error", reason} + }); + resp->setBody(json.to_json_string()); + cb(resp); + } + }, + {drogon::Post} + ); + + + svr.Post(api + "get_debug_log", [](HTTP_Param) { + CHECK_JSON_PARAM + HTTP_REQUIRE_VALUE(start_serial_number, params.try_get_number("serial_number")) + auto log = base64_encode(debug_logger_info_manager.get_info(start_serial_number)); + JSON ret = JSON::object(); + ret.append({"log", log}); + ret.append({"serial_number", debug_logger_info_manager.get_cur_serial_number()}); + ret.append({"start_serial_number", start_serial_number}); + res->setBody(warp(ret).to_json_string()); + }); + + + svr.Post(api + "get_general_info", [](HTTP_Param) { + auto g = Global::instance(); + // auto &db = *Global::instance()->total_source(); + auto ret = JSON::object(); + // ret.append({"total_source key", db.key}); + // ret.append({"飞机总数", db.get_aircraft_num()}); + // ret.append({"解出位置的数量", db.have_pos_aircraft_num}); + JSON arr = JSON::object(); + for (auto &ds: g->mode_acs.data_source_config.map.list()) { + if (!ds->enable) continue; + arr.append({ds->key, ds->statistic_json()}); + } + ret.append({"统计信息", arr}); + // ret.append({"基站信息", base_station.to_Json()}); + res->setBody(warp(ret).to_json_string()); + }); + + + + + svr.Post(api + "version", [this](HTTP_Param) { + auto ret = JSON::object(); + ret.append({"version", version}); + res->setBody(warp(ret).to_json_string()); + }); + + + auto now = std::chrono::system_clock::now(); + start_server_time = std::chrono::system_clock::to_time_t(now); + svr.Post(api + "start_device_time", [this](HTTP_Param) { + auto ret = JSON::object(); + ret.append({"start_device_time", start_server_time}); + res->setBody(warp(ret).to_json_string()); + }); + + da.setDefaultHandler( + [](const drogon::HttpRequestPtr& req, + std::function&& callback) { + LOG_WARN + << "[http route not found] " + << "path=" << req->path(); + + auto res = drogon::HttpResponse::newHttpResponse(); + + res->setStatusCode(drogon::k404NotFound); + res->setBody("not found"); + res->addHeader("Content-Type", "text/plain; charset=utf-8"); + + callback(res); + } + ); + + da.setCustomErrorHandler( + [](drogon::HttpStatusCode status, + const drogon::HttpRequestPtr& req) -> drogon::HttpResponsePtr { + LOG_WARN + << "[http error] " + << "path=" << req->path() + << ", status=" << static_cast(status); + + auto res = drogon::HttpResponse::newHttpResponse(); + + res->setStatusCode(status); + res->setBody("http error"); + res->addHeader("Content-Type", "text/plain; charset=utf-8"); + + return res; + } + ); + + da.setExceptionHandler( + [](const std::exception& e, + const drogon::HttpRequestPtr& req, + std::function&& callback) { + auto res = drogon::HttpResponse::newHttpResponse(); + + if (Is_Invalid_Http_Param(e)) { + LOG_WARN + << "[invalid http parameters] " + << "path=" << req->path() + << ", detail=" << e.what(); + Log_Request_Body_If_Small(req); + + res->setStatusCode(drogon::k400BadRequest); + res->setContentTypeCode(drogon::CT_APPLICATION_JSON); + res->setBody(R"({"code":400,"message":"invalid http parameters"})"); + } + else { + LOG_ERROR + << "[handler exception] " + << "path=" << req->path() + << ", what=" << e.what(); + + res->setStatusCode(drogon::k500InternalServerError); + res->setContentTypeCode(drogon::CT_APPLICATION_JSON); + res->setBody(R"({"code":500,"message":"internal server error"})"); + } + + callback(res); + } + ); + + + da.setUploadPath(get_exe_dir() + "/drogon_uploads"); + std::cout << "上传路径: " << da.getUploadPath() << std::endl; + + + + init_ok = true; +} + +void setupCors() { + // Register sync advice to handle CORS preflight (OPTIONS) requests + drogon::app().registerSyncAdvice([](const drogon::HttpRequestPtr &req) -> drogon::HttpResponsePtr { + if (req->method() == drogon::HttpMethod::Options) { + auto resp = drogon::HttpResponse::newHttpResponse(); + + // Set Access-Control-Allow-Origin header based on the Origin + // request header + const auto &origin = req->getHeader("Origin"); + if (!origin.empty()) { + resp->addHeader("Access-Control-Allow-Origin", origin); + } + + // Set Access-Control-Allow-Methods based on the requested method + const auto &requestMethod = req->getHeader("Access-Control-Request-Method"); + if (!requestMethod.empty()) { + resp->addHeader("Access-Control-Allow-Methods", requestMethod); + } + + // Allow credentials to be included in cross-origin requests + resp->addHeader("Access-Control-Allow-Credentials", "true"); + + // Set allowed headers from the Access-Control-Request-Headers + // header + const auto &requestHeaders = req->getHeader("Access-Control-Request-Headers"); + if (!requestHeaders.empty()) { + resp->addHeader("Access-Control-Allow-Headers", requestHeaders); + } + + return std::move(resp); + } + return {}; + }); + + // Register post-handling advice to add CORS headers to all responses + drogon::app().registerPostHandlingAdvice( + [](const drogon::HttpRequestPtr &req, const drogon::HttpResponsePtr &resp) -> void { + // Set Access-Control-Allow-Origin based on the Origin request + // header + const auto &origin = req->getHeader("Origin"); + if (!origin.empty()) { + resp->addHeader("Access-Control-Allow-Origin", origin); + } + + // Reflect the requested Access-Control-Request-Method back in the + // response + const auto &requestMethod = req->getHeader("Access-Control-Request-Method"); + if (!requestMethod.empty()) { + resp->addHeader("Access-Control-Allow-Methods", requestMethod); + } + + // Allow credentials to be included in cross-origin requests + resp->addHeader("Access-Control-Allow-Credentials", "true"); + + // Reflect the requested Access-Control-Request-Headers back + const auto &requestHeaders = req->getHeader("Access-Control-Request-Headers"); + if (!requestHeaders.empty()) { + resp->addHeader("Access-Control-Allow-Headers", requestHeaders); + } + }); +} diff --git a/module/Local_Server_main.cpp b/module/Local_Server_main.cpp new file mode 100644 index 0000000..2dfdc57 --- /dev/null +++ b/module/Local_Server_main.cpp @@ -0,0 +1,229 @@ +#include +#include + +#include "Local_Server/Crawler/Crawler.h" +#include "Local_Server/Data_Source/Data_Source.h" +#include "Local_Server/State_Report/State_Report.h" +#include "Local_Server/server/Global.h" + +#include +#include +#include +#include +#include +// ./server ./config.json + +#ifdef __linux__ +#include +#endif + +struct Catch_Memory { + Catch_Memory() { + sm = (std::int64_t)get_system_memory(); + cur = (std::int64_t)get_process_memory(); + } + std::int64_t MB = 1024 * 1024; + bool catch_ok() { + auto old_memory = cur; + cur = (std::int64_t)get_process_memory(); + auto change_mb = (cur - old_memory) / MB; + auto cur_mb = cur / MB; + // std::cout << get_current_date_string() << " 时内存" << cur_mb << "MB " + // << " 变更" << change_mb << "MB" << std::endl; + + if (cur > memory_max) { + auto rate = (double)cur / (double)sm; + std::cout << get_current_date_string() + " 抓住内存暴涨:" << cur_mb + << "MB" << " 变更" << change_mb << "MB" << std::endl; + std::cout << "\t内存占用" << rate << "%" << std::endl; + stop_program = SIGINT; + +#if WIN32 +#else + system("top"); +#endif + return true; + } + return false; + } +#if WIN32 + std::int64_t memory_max = 1 * 1024 * 1024 * 1024; +#else + std::int64_t memory_max = 1 * 1024 * 1024 * 1024; +#endif + std::int64_t cur; + std::int64_t sm; +}; + +int wyc_main(int argc, char *argv[]) { + std::cout << "wyc_main" << std::endl; + + if (argc >= 2) { + std::string config_path = argv[1]; + if (config_path.at(0) == '@') { + config_path.replace(0, 1, get_exe_dir()); + } + ::default_config_path = config_path; + } + + // std::cout << get_stack_trace() << std::endl; + + init_signal_config({SIGINT, SIGTERM}, +#ifdef WIN32 + {} +#else + {SIGPIPE} +#endif + ); + auto clear = []() { + std::cout << "保存配置文件 " << LOG_POS << std::endl; + Config::save(); + std::cout << "shutdown" << std::endl; + + auto i = Global::instance(); + i->svr.stop(); + i->thread_manager.stop_all_thread(); + + Global::instance()->dsp_config.close_dsp_device(); + std::cout << "destroy开始" << std::endl; + Global::destroy(); + std::cout << " Base_Logger_Manager::instance()->destroy()开始" << std::endl; + Base_Logger_Manager::destroy(); + std::cout << " clear完成" << std::endl; + std::cout << " 正常退出" << std::endl; + }; + + + auto g = Global::instance(); + Psc::set_fail_fast([](const std::string& name) { + // 如果是在 Global::instance() 里面失败是不能调用这个处理函数的 + auto g = Global::instance(); + std::cout << "set_fail_fast 退出原因: 222" << name << std::endl; + std::cout << "shutdown" << LOG_POS << std::endl; + + + auto id = std::this_thread::get_id(); + std::cout << "id: " << id << std::endl; + + + + Detach_Thread *dt = g->thread_manager.get_thread(id); + if (!dt) + { + std::cout << VAR_STR_1((void*)dt) << std::endl; + std::terminate(); + } + + + std::set excluded_thread; + excluded_thread.insert(dt->name); + std::cout << dt->name << std::endl; + + + + // std::cout << "保存配置文件 " << LOG_POS << std::endl; + // Config::save(); + + + + g->svr.stop(); + g->thread_manager.stop_all_thread(10000, excluded_thread); + std::cout << "22222222" << std::endl; + Global::instance()->dsp_config.close_dsp_device(); + + std::cout << "destroy开始" << std::endl; + Global::destroy(); + std::cout << " Base_Logger_Manager::instance()->destroy()开始" << std::endl; + Base_Logger_Manager::destroy(); + std::cout << " clear完成" << std::endl; + std::cout << " std::exit(75) " << name << std::endl; + std::exit(75); + std::cout << "std::exit(75) 完成:" << name << std::endl; + }); + + + + Detach_Thread_Manager &manager = g->thread_manager; + std::cout << "[主线程:" << std::this_thread::get_id() << "]" << " \n" + << std::flush; + manager.test_and_start_thread( + "web服务器线程", [g](std::atomic &running) { + auto &c = Global::instance()->device_config; + std::string ip = "0.0.0.0"; +#ifdef WIN32 + ip = "127.0.0.1"; +#endif + Net_Config *device = c.list.get("device").value(); + std::cout << "http://" + ip + ":" + std::to_string(device->port) + "\n" + << std::flush; + std::cout << "http://" + device->ip + ":" + + std::to_string(device->port) + "\n" + << std::flush; + if (!g->svr.listen("0.0.0.0", device->port)) { + if (running.load(std::memory_order_acquire)) { + std::cout << "http://" + ip + ":" + std::to_string(device->port) + + "启动失败 退出!\n" + << std::flush; + std::cout << "http://" + device->ip + ":" + + std::to_string(device->port) + "启动失败 退出!\n" + << std::flush; + std::exit(0); + } + } + }); + manager.test_and_start_thread("data_feed_thread", data_feed_thread); + static int t = Global::instance()->mode_acs.read_milliseconds; + for (auto &source : + Global::instance()->mode_acs.data_source_config.map.list()) { + if (!source->enable) + continue; + source->test_and_attach_thread(); + } + g->dsp_config.init_env(); + + bool enable = g->mlat.enable; + + // Catch_Memory cm; + while (stop_program == 0) { + for (auto &ds : g->mode_acs.data_source_config.map.list()) { + ds->delete_timeout_aircraft(); + } + + if (enable) { + g->mlat.refresh(); + } +#ifdef __linux__ + if (g->mode_acs.monitor_msg_live) + malloc_trim(0); +#endif + std::this_thread::sleep_for(std::chrono::seconds(3)); + if (Global::instance()->init_ok) { + // if (cm.catch_ok()) { + // stop_program = SIGINT; + // break; + // } + } + } + + if (stop_program == SIGINT || stop_program == SIGTERM) { + clear(); + } + + return 0; +} + + +int main(int argc, char* argv[]) { + return redict_main_with_gtest(argc, argv, wyc_main); +} + + + + +// int main(int argc, char* argv[]) { +// +// Psc::set_console_utf8(); +// std::cout << "asio 起点" << std::endl; +// +// return 0; +// } \ No newline at end of file diff --git a/module/Mock/main.cpp b/module/Mock/main.cpp new file mode 100644 index 0000000..bcf534c --- /dev/null +++ b/module/Mock/main.cpp @@ -0,0 +1,56 @@ +#include +#include "mock_core.h" +int main() { + std::cout << "Hello World!\n"; + + std::vector stations; + auto yan_tai = new Mock::BaseStation(); + yan_tai->set_pos(37.5102686, 121.4399498); + stations.push_back(yan_tai); + auto qing_dao = new Mock::BaseStation(); + qing_dao->set_pos(36.0759435, 120.4085225); + stations.push_back(qing_dao); + auto ji_nan = new Mock::BaseStation(); + ji_nan->set_pos(36.6513057, 116.9837594); + stations.push_back(ji_nan); + + + + Mock::Plane plane; + plane.icao = "ABCDEF"; + while (true) { + MLAT_timestamp cur; + + for (auto station : stations) + { + auto i = plane.generate_message(&cur, {37, 121}, station); + auto t = station->generage_message(); + std::cout << memory2hex(i) << std::endl; + std::cout << memory2hex(t) << std::endl; + } + + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + } + + + return 0; +} + + + + +// Mock::BaseStation b; +// b.set_pos(37, 137); +// +// auto mem = b.generage_message(); +// +// auto mem2 = unescape_packet(mem); +// std::cout << mem << std::endl; +// +// std::cout << mem2.size() << " parse_hex:" << memory2hex(mem2) << std::endl; +// +// HULC_Status_Message message = create_HULC_Status_Message(mem2); +// +// std::cout << "完全相等" << bool (unescape_packet(message.toBinary()) == mem2) << std::endl; diff --git a/module/Mock/mock_core.cpp b/module/Mock/mock_core.cpp new file mode 100644 index 0000000..15fc5e4 --- /dev/null +++ b/module/Mock/mock_core.cpp @@ -0,0 +1 @@ +#include "mock_core.h" diff --git a/module/Mock/mock_core.h b/module/Mock/mock_core.h new file mode 100644 index 0000000..13942dc --- /dev/null +++ b/module/Mock/mock_core.h @@ -0,0 +1,84 @@ +#ifndef MOCK_CORE_H +#define MOCK_CORE_H +#include + + +// 通过构造 三个基站 以及 一个飞机 三个数据源 来生成数据 + +namespace Mock { + struct BaseStation { + Psc::socket::Server_Socket server; + HULC_Status_Message hulc_message{}; + // 生成HUCL消息报告 1a34 自身经纬度 1s 1次 + BaseStation() { + hulc_message.SerNum = 3; + hulc_message.Alt = 7500; + hulc_message.HDOP = 0.75; + hulc_message.Sat = 75; + hulc_message.I_U_ = 88; + } + + std::string generage_message() { + auto now = std::chrono::system_clock::now(); + auto epoch = std::chrono::system_clock::to_time_t(now); + hulc_message.xTime = epoch; + // 要经过转义 + return packet_to_escape_format(hulc_message.toBinary()); + } + + void set_pos(CPR::D lat, CPR::D lon) { + hulc_message.set_latitude(lat); + hulc_message.set_longitude(lon); + } + [[nodiscard]] CPR::Position position() const { + CPR::Position pos{}; + pos.latitude = hulc_message.get_latitude(); + pos.longitude = hulc_message.get_longitude(); + return pos; + } + }; + + class Plane { + public: + std::string icao; + std::vector track; + std::string generate_message(MLAT_timestamp* timestamp, CPR::Position pos, BaseStation* station) { + All_Call_Reply::All_Call_Reply r = All_Call_Reply::All_Call_Reply::Builder() + .set_icao(icao) + .set_interrogator_identifier("abc750") + .set_transponder_address(hex2bin("abc617")) + .build(); + + + auto [i1, i2] = CPR::light_time_second_nano(station->position(), pos); + auto total_nanosec = timestamp->nanosec + i2; // 累加纳秒部分 + auto total_sec = timestamp->daysec + i1; // 累加秒部分 + if (total_nanosec >= 1000000000) { + // 纳秒部分超过 1 秒,处理进位 + total_sec += total_nanosec / 1000000000; // 将纳秒转换为秒并加到 total_sec + total_nanosec = total_nanosec % 1000000000; // 保留纳秒部分,取余数 + } + MLAT_timestamp t(total_sec, total_nanosec); + // 默认构造获取系统的描述 已经经过转义 + return create_Binary_Format_memory(bin2hex(r.msg), 75, &t); + } + }; + + + struct Global { + std::vector planes; + + + + std::vector stations; + + }; + + +} + + + + + +#endif diff --git a/module/Radarcape_Core/cpp-httplib/doc.txt b/module/Radarcape_Core/cpp-httplib/doc.txt new file mode 100644 index 0000000..b777ae8 --- /dev/null +++ b/module/Radarcape_Core/cpp-httplib/doc.txt @@ -0,0 +1 @@ +https://github.com/yhirose/cpp-httplib \ No newline at end of file diff --git a/module/Radarcape_Core/cpp-httplib/httplib.h b/module/Radarcape_Core/cpp-httplib/httplib.h new file mode 100644 index 0000000..c068a65 --- /dev/null +++ b/module/Radarcape_Core/cpp-httplib/httplib.h @@ -0,0 +1,11698 @@ +// +// httplib.h +// +// Copyright (c) 2024 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.18.2" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_RANGE_MAX_COUNT +#define CPPHTTPLIB_RANGE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_IPV6_V6ONLY +#define CPPHTTPLIB_IPV6_V6ONLY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#if !defined(_AIX) && !defined(__MVS__) +#include +#endif +#ifdef __MVS__ +#include +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER < 0x1010107f +#error Please use OpenSSL or a current version of BoringSSL +#endif +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#error Sorry, OpenSSL versions prior to 3.0.0 are not supported +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib +{ + namespace detail + { + /* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + + template + typename std::enable_if::value, std::unique_ptr>::type + make_unique(Args&&... args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } + + template + typename std::enable_if::value, std::unique_ptr>::type + make_unique(std::size_t n) + { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); + } + + namespace case_ignore + { + inline unsigned char to_lower(int c) + { + const static unsigned char table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255, + }; + return table[(unsigned char)(char)c]; + } + + inline bool equal(const std::string& a, const std::string& b) + { + return a.size() == b.size() && + std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) + { + return to_lower(ca) == to_lower(cb); + }); + } + + struct equal_to + { + bool operator()(const std::string& a, const std::string& b) const + { + return equal(a, b); + } + }; + + struct hash + { + size_t operator()(const std::string& key) const + { + return hash_core(key.data(), key.size(), 0); + } + + size_t hash_core(const char* s, size_t l, size_t h) const + { + return (l == 0) + ? h + : hash_core(s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no + // overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(to_lower(*s))); + } + }; + } // namespace case_ignore + + // This is based on + // "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + + struct scope_exit + { + explicit scope_exit(std::function&& f) : + exit_function(std::move(f)), execute_on_destruction{true} + { + } + + scope_exit(scope_exit&& rhs) noexcept : + exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} + { + rhs.release(); + } + + ~scope_exit() + { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + + private: + scope_exit(const scope_exit&) = delete; + void operator=(const scope_exit&) = delete; + scope_exit& operator=(scope_exit&&) = delete; + + std::function exit_function; + bool execute_on_destruction; + }; + } // namespace detail + + enum StatusCode + { + // Information responses + Continue_100 = 100, + SwitchingProtocol_101 = 101, + Processing_102 = 102, + EarlyHints_103 = 103, + + // Successful responses + OK_200 = 200, + Created_201 = 201, + Accepted_202 = 202, + NonAuthoritativeInformation_203 = 203, + NoContent_204 = 204, + ResetContent_205 = 205, + PartialContent_206 = 206, + MultiStatus_207 = 207, + AlreadyReported_208 = 208, + IMUsed_226 = 226, + + // Redirection messages + MultipleChoices_300 = 300, + MovedPermanently_301 = 301, + Found_302 = 302, + SeeOther_303 = 303, + NotModified_304 = 304, + UseProxy_305 = 305, + unused_306 = 306, + TemporaryRedirect_307 = 307, + PermanentRedirect_308 = 308, + + // Client error responses + BadRequest_400 = 400, + Unauthorized_401 = 401, + PaymentRequired_402 = 402, + Forbidden_403 = 403, + NotFound_404 = 404, + MethodNotAllowed_405 = 405, + NotAcceptable_406 = 406, + ProxyAuthenticationRequired_407 = 407, + RequestTimeout_408 = 408, + Conflict_409 = 409, + Gone_410 = 410, + LengthRequired_411 = 411, + PreconditionFailed_412 = 412, + PayloadTooLarge_413 = 413, + UriTooLong_414 = 414, + UnsupportedMediaType_415 = 415, + RangeNotSatisfiable_416 = 416, + ExpectationFailed_417 = 417, + ImATeapot_418 = 418, + MisdirectedRequest_421 = 421, + UnprocessableContent_422 = 422, + Locked_423 = 423, + FailedDependency_424 = 424, + TooEarly_425 = 425, + UpgradeRequired_426 = 426, + PreconditionRequired_428 = 428, + TooManyRequests_429 = 429, + RequestHeaderFieldsTooLarge_431 = 431, + UnavailableForLegalReasons_451 = 451, + + // Server error responses + InternalServerError_500 = 500, + NotImplemented_501 = 501, + BadGateway_502 = 502, + ServiceUnavailable_503 = 503, + GatewayTimeout_504 = 504, + HttpVersionNotSupported_505 = 505, + VariantAlsoNegotiates_506 = 506, + InsufficientStorage_507 = 507, + LoopDetected_508 = 508, + NotExtended_510 = 510, + NetworkAuthenticationRequired_511 = 511, + }; + + using Headers = + std::unordered_multimap; + + using Params = std::multimap; + using Match = std::smatch; + + using Progress = std::function; + + struct Response; + using ResponseHandler = std::function; + + struct MultipartFormData + { + std::string name; + std::string content; + std::string filename; + std::string content_type; + }; + + using MultipartFormDataItems = std::vector; + using MultipartFormDataMap = std::multimap; + + class DataSink + { + public: + DataSink() : + os(&sb_), sb_(*this) + { + } + + DataSink(const DataSink&) = delete; + DataSink& operator=(const DataSink&) = delete; + DataSink(DataSink&&) = delete; + DataSink& operator=(DataSink&&) = delete; + + std::function write; + std::function is_writable; + std::function done; + std::function done_with_trailer; + std::ostream os; + + private: + class data_sink_streambuf final : public std::streambuf + { + public: + explicit data_sink_streambuf(DataSink& sink) : + sink_(sink) + { + } + + protected: + std::streamsize xsputn(const char* s, std::streamsize n) override + { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink& sink_; + }; + + data_sink_streambuf sb_; + }; + + using ContentProvider = + std::function; + + using ContentProviderWithoutLength = + std::function; + + using ContentProviderResourceReleaser = std::function; + + struct MultipartFormDataProvider + { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; + }; + + using MultipartFormDataProviderItems = std::vector; + + using ContentReceiverWithProgress = + std::function; + + using ContentReceiver = + std::function; + + using MultipartContentHeader = + std::function; + + class ContentReader + { + public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) : + reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) + { + } + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const + { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const + { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; + }; + + using Range = std::pair; + using Ranges = std::vector; + + struct Request + { + std::string method; + std::string path; + Params params; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + std::unordered_map path_params; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string& key) const; + std::string get_header_value(const std::string& key, const char* def = "", + size_t id = 0) const; + uint64_t get_header_value_u64(const std::string& key, uint64_t def = 0, + size_t id = 0) const; + size_t get_header_value_count(const std::string& key) const; + void set_header(const std::string& key, const std::string& val); + + bool has_param(const std::string& key) const; + std::string get_param_value(const std::string& key, size_t id = 0) const; + size_t get_param_value_count(const std::string& key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string& key) const; + MultipartFormData get_file_value(const std::string& key) const; + std::vector get_file_values(const std::string& key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; + }; + + struct Response + { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string& key) const; + std::string get_header_value(const std::string& key, const char* def = "", + size_t id = 0) const; + uint64_t get_header_value_u64(const std::string& key, uint64_t def = 0, + size_t id = 0) const; + size_t get_header_value_count(const std::string& key) const; + void set_header(const std::string& key, const std::string& val); + + void set_redirect(const std::string& url, int status = StatusCode::Found_302); + void set_content(const char* s, size_t n, const std::string& content_type); + void set_content(const std::string& s, const std::string& content_type); + void set_content(std::string&& s, const std::string& content_type); + + void set_content_provider( + size_t length, const std::string& content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string& content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string& content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_file_content(const std::string& path, + const std::string& content_type); + void set_file_content(const std::string& path); + + Response() = default; + Response(const Response&) = default; + Response& operator=(const Response&) = default; + Response(Response&&) = default; + Response& operator=(Response&&) = default; + + ~Response() + { + if (content_provider_resource_releaser_) + { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; + std::string file_content_path_; + std::string file_content_content_type_; + }; + + class Stream + { + public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char* ptr, size_t size) = 0; + virtual ssize_t write(const char* ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string& ip, int& port) const = 0; + virtual void get_local_ip_and_port(std::string& ip, int& port) const = 0; + virtual socket_t socket() const = 0; + + ssize_t write(const char* ptr); + ssize_t write(const std::string& s); + }; + + class TaskQueue + { + public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual bool enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() + { + } + }; + + class ThreadPool final : public TaskQueue + { + public: + explicit ThreadPool(size_t n, size_t mqr = 0) : + shutdown_(false), max_queued_requests_(mqr) + { + while (n) + { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool&) = delete; + ~ThreadPool() override = default; + + bool enqueue(std::function fn) override + { + { + std::unique_lock lock(mutex_); + if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) + { + return false; + } + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + return true; + } + + void shutdown() override + { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto& t : threads_) + { + t.join(); + } + } + + private: + struct worker + { + explicit worker(ThreadPool& pool) : + pool_(pool) + { + } + + void operator()() + { + for (;;) + { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = pool_.jobs_.front(); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + +#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) + OPENSSL_thread_stop(); +#endif + } + + ThreadPool& pool_; + }; + + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + size_t max_queued_requests_ = 0; + + std::condition_variable cond_; + std::mutex mutex_; + }; + + using Logger = std::function; + + using SocketOptions = std::function; + + void default_socket_options(socket_t sock); + + const char* status_message(int status); + + std::string get_bearer_token_auth(const Request& req); + + namespace detail + { + class MatcherBase + { + public: + virtual ~MatcherBase() = default; + + // Match request path and populate its matches and + virtual bool match(Request& request) const = 0; + }; + + /** + * Captures parameters in request path and stores them in Request::path_params + * + * Capture name is a substring of a pattern from : to /. + * The rest of the pattern is matched agains the request path directly + * Parameters are captured starting from the next character after + * the end of the last matched static pattern fragment until the next /. + * + * Example pattern: + * "/path/fragments/:capture/more/fragments/:second_capture" + * Static fragments: + * "/path/fragments/", "more/fragments/" + * + * Given the following request path: + * "/path/fragments/:1/more/fragments/:2" + * the resulting capture will be + * {{"capture", "1"}, {"second_capture", "2"}} + */ + class PathParamsMatcher final : public MatcherBase + { + public: + PathParamsMatcher(const std::string& pattern); + + bool match(Request& request) const override; + + private: + // Treat segment separators as the end of path parameter capture + // Does not need to handle query parameters as they are parsed before path + // matching + static constexpr char separator = '/'; + + // Contains static path fragments to match against, excluding the '/' after + // path params + // Fragments are separated by path params + std::vector static_fragments_; + // Stores the names of the path parameters to be used as keys in the + // Request::path_params map + std::vector param_names_; + }; + + /** + * Performs std::regex_match on request path + * and stores the result in Request::matches + * + * Note that regex match is performed directly on the whole request. + * This means that wildcard patterns may match multiple path segments with /: + * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". + */ + class RegexMatcher final : public MatcherBase + { + public: + RegexMatcher(const std::string& pattern) : + regex_(pattern) + { + } + + bool match(Request& request) const override; + + private: + std::regex regex_; + }; + + ssize_t write_headers(Stream& strm, const Headers& headers); + } // namespace detail + + class Server + { + public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse + { + Handled, + Unhandled, + }; + + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server& Get(const std::string& pattern, Handler handler); + Server& Post(const std::string& pattern, Handler handler); + Server& Post(const std::string& pattern, HandlerWithContentReader handler); + Server& Put(const std::string& pattern, Handler handler); + Server& Put(const std::string& pattern, HandlerWithContentReader handler); + Server& Patch(const std::string& pattern, Handler handler); + Server& Patch(const std::string& pattern, HandlerWithContentReader handler); + Server& Delete(const std::string& pattern, Handler handler); + Server& Delete(const std::string& pattern, HandlerWithContentReader handler); + Server& Options(const std::string& pattern, Handler handler); + + bool set_base_dir(const std::string& dir, + const std::string& mount_point = std::string()); + bool set_mount_point(const std::string& mount_point, const std::string& dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string& mount_point); + Server& set_file_extension_and_mimetype_mapping(const std::string& ext, + const std::string& mime); + Server& set_default_file_mimetype(const std::string& mime); + Server& set_file_request_handler(Handler handler); + + template + Server& set_error_handler(ErrorHandlerFunc&& handler) + { + return set_error_handler_core( + std::forward(handler), + std::is_convertible{}); + } + + Server& set_exception_handler(ExceptionHandler handler); + Server& set_pre_routing_handler(HandlerWithResponse handler); + Server& set_post_routing_handler(Handler handler); + + Server& set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server& set_logger(Logger logger); + + Server& set_address_family(int family); + Server& set_tcp_nodelay(bool on); + Server& set_ipv6_v6only(bool on); + Server& set_socket_options(SocketOptions socket_options); + + Server& set_default_headers(Headers headers); + Server& + set_header_writer(std::function const& writer); + + Server& set_keep_alive_max_count(size_t count); + Server& set_keep_alive_timeout(time_t sec); + + Server& set_read_timeout(time_t sec, time_t usec = 0); + template + Server& set_read_timeout(const std::chrono::duration& duration); + + Server& set_write_timeout(time_t sec, time_t usec = 0); + template + Server& set_write_timeout(const std::chrono::duration& duration); + + Server& set_idle_interval(time_t sec, time_t usec = 0); + template + Server& set_idle_interval(const std::chrono::duration& duration); + + Server& set_payload_max_length(size_t length); + + bool bind_to_port(const std::string& host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string& host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string& host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + void decommission(); + + std::function new_task_queue; + + protected: + bool process_request(Stream& strm, const std::string& remote_addr, + int remote_port, const std::string& local_addr, + int local_port, bool close_connection, + bool& connection_closed, + const std::function& setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + + private: + using Handlers = + std::vector, Handler>>; + using HandlersForContentReader = + std::vector, + HandlerWithContentReader>>; + + static std::unique_ptr + make_matcher(const std::string& pattern); + + Server& set_error_handler_core(HandlerWithResponse handler, std::true_type); + Server& set_error_handler_core(Handler handler, std::false_type); + + socket_t create_server_socket(const std::string& host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string& host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request& req, Response& res, Stream& strm); + bool handle_file_request(const Request& req, Response& res, + bool head = false); + bool dispatch_request(Request& req, Response& res, + const Handlers& handlers) const; + bool dispatch_request_for_content_reader( + Request& req, Response& res, ContentReader content_reader, + const HandlersForContentReader& handlers) const; + + bool parse_request_line(const char* s, Request& req) const; + void apply_ranges(const Request& req, Response& res, + std::string& content_type, std::string& boundary) const; + bool write_response(Stream& strm, bool close_connection, Request& req, + Response& res); + bool write_response_with_content(Stream& strm, bool close_connection, + const Request& req, Response& res); + bool write_response_core(Stream& strm, bool close_connection, + const Request& req, Response& res, + bool need_apply_ranges); + bool write_content_with_provider(Stream& strm, const Request& req, + Response& res, const std::string& boundary, + const std::string& content_type); + bool read_content(Stream& strm, Request& req, Response& res); + bool + read_content_with_content_receiver(Stream& strm, Request& req, Response& res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream& strm, Request& req, Response& res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) const; + + virtual bool process_and_close_socket(socket_t sock); + + std::atomic is_running_{false}; + std::atomic is_decommisioned{false}; + + struct MountPointEntry + { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + + std::vector base_dirs_; + std::map file_extension_and_mimetype_map_; + std::string default_file_mimetype_ = "application/octet-stream"; + Handler file_request_handler_; + + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Expect100ContinueHandler expect_100_continue_handler_; + + Logger logger_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; + std::function header_writer_ = + detail::write_headers; + }; + + enum class Error + { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + SSLServerHostnameVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + ProxyConnection, + + // For internal use only + SSLPeerCouldBeClosed_, + }; + + std::string to_string(Error error); + + std::ostream& operator<<(std::ostream& os, const Error& obj); + + class Result + { + public: + Result() = default; + + Result(std::unique_ptr&& res, Error err, + Headers&& request_headers = Headers{}) : + res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) + { + } + + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response& value() const { return *res_; } + Response& value() { return *res_; } + const Response& operator*() const { return *res_; } + Response& operator*() { return *res_; } + const Response* operator->() const { return res_.get(); } + Response* operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string& key) const; + std::string get_request_header_value(const std::string& key, + const char* def = "", + size_t id = 0) const; + uint64_t get_request_header_value_u64(const std::string& key, + uint64_t def = 0, size_t id = 0) const; + size_t get_request_header_value_count(const std::string& key) const; + + private: + std::unique_ptr res_; + Error err_ = Error::Unknown; + Headers request_headers_; + }; + + class ClientImpl + { + public: + explicit ClientImpl(const std::string& host); + + explicit ClientImpl(const std::string& host, int port); + + explicit ClientImpl(const std::string& host, int port, + const std::string& client_cert_path, + const std::string& client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const std::string& path); + Result Get(const std::string& path, const Headers& headers); + Result Get(const std::string& path, Progress progress); + Result Get(const std::string& path, const Headers& headers, + Progress progress); + Result Get(const std::string& path, ContentReceiver content_receiver); + Result Get(const std::string& path, const Headers& headers, + ContentReceiver content_receiver); + Result Get(const std::string& path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string& path, const Headers& headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string& path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string& path, const Headers& headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string& path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string& path, const Headers& headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string& path, const Params& params, + const Headers& headers, Progress progress = nullptr); + Result Get(const std::string& path, const Params& params, + const Headers& headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string& path, const Params& params, + const Headers& headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string& path); + Result Head(const std::string& path, const Headers& headers); + + Result Post(const std::string& path); + Result Post(const std::string& path, const Headers& headers); + Result Post(const std::string& path, const char* body, size_t content_length, + const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, const char* body, + size_t content_length, const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, const char* body, + size_t content_length, const std::string& content_type, + Progress progress); + Result Post(const std::string& path, const std::string& body, + const std::string& content_type); + Result Post(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress); + Result Post(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type, + Progress progress); + Result Post(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type); + Result Post(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, + size_t content_length, ContentProvider content_provider, + const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Post(const std::string& path, const Params& params); + Result Post(const std::string& path, const Headers& headers, + const Params& params); + Result Post(const std::string& path, const Headers& headers, + const Params& params, Progress progress); + Result Post(const std::string& path, const MultipartFormDataItems& items); + Result Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items); + Result Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, const std::string& boundary); + Result Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items); + + Result Put(const std::string& path); + Result Put(const std::string& path, const char* body, size_t content_length, + const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, const char* body, + size_t content_length, const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, const char* body, + size_t content_length, const std::string& content_type, + Progress progress); + Result Put(const std::string& path, const std::string& body, + const std::string& content_type); + Result Put(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress); + Result Put(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type, + Progress progress); + Result Put(const std::string& path, size_t content_length, + ContentProvider content_provider, const std::string& content_type); + Result Put(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, + size_t content_length, ContentProvider content_provider, + const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Put(const std::string& path, const Params& params); + Result Put(const std::string& path, const Headers& headers, + const Params& params); + Result Put(const std::string& path, const Headers& headers, + const Params& params, Progress progress); + Result Put(const std::string& path, const MultipartFormDataItems& items); + Result Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items); + Result Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, const std::string& boundary); + Result Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items); + + Result Patch(const std::string& path); + Result Patch(const std::string& path, const char* body, size_t content_length, + const std::string& content_type); + Result Patch(const std::string& path, const char* body, size_t content_length, + const std::string& content_type, Progress progress); + Result Patch(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type); + Result Patch(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, Progress progress); + Result Patch(const std::string& path, const std::string& body, + const std::string& content_type); + Result Patch(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress); + Result Patch(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type); + Result Patch(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type, + Progress progress); + Result Patch(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type); + Result Patch(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Patch(const std::string& path, const Headers& headers, + size_t content_length, ContentProvider content_provider, + const std::string& content_type); + Result Patch(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + + Result Delete(const std::string& path); + Result Delete(const std::string& path, const Headers& headers); + Result Delete(const std::string& path, const char* body, + size_t content_length, const std::string& content_type); + Result Delete(const std::string& path, const char* body, + size_t content_length, const std::string& content_type, + Progress progress); + Result Delete(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type); + Result Delete(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, Progress progress); + Result Delete(const std::string& path, const std::string& body, + const std::string& content_type); + Result Delete(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress); + Result Delete(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type); + Result Delete(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type, + Progress progress); + + Result Options(const std::string& path); + Result Options(const std::string& path, const Headers& headers); + + bool send(Request& req, Response& res, Error& error); + Result send(const Request& req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const& writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_ipv6_v6only(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration& duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration& duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration& duration); + + void set_basic_auth(const std::string& username, const std::string& password); + void set_bearer_token_auth(const std::string& token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string& intf); + + void set_proxy(const std::string& host, int port); + void set_proxy_basic_auth(const std::string& username, + const std::string& password); + void set_proxy_bearer_token_auth(const std::string& token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); + X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier(std::function verifier); +#endif + + void set_logger(Logger logger); + + protected: + struct Socket + { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket& socket, Error& error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket& socket, bool shutdown_gracefully); + void shutdown_socket(Socket& socket) const; + void close_socket(Socket& socket); + + bool process_request(Stream& strm, Request& req, Response& res, + bool close_connection, Error& error); + + bool write_content_with_provider(Stream& strm, const Request& req, + Error& error) const; + + void copy_settings(const ClientImpl& rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Header writer + std::function header_writer_ = + detail::write_headers; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; + bool server_hostname_verification_ = true; + std::function server_certificate_verifier_; +#endif + + Logger logger_; + + private: + bool send_(Request& req, Response& res, Error& error); + Result send_(Request&& req); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_peer_could_be_closed(SSL *ssl) const; +#endif + socket_t create_client_socket(Error& error) const; + bool read_response_line(Stream& strm, const Request& req, + Response& res) const; + bool write_request(Stream& strm, Request& req, bool close_connection, + Error& error); + bool redirect(Request& req, Response& res, Error& error); + bool handle_request(Stream& strm, Request& req, Response& res, + bool close_connection, Error& error); + std::unique_ptr send_with_content_provider( + Request& req, const char* body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string& content_type, Error& error); + Result send_with_content_provider( + const std::string& method, const std::string& path, + const Headers& headers, const char* body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string& content_type, Progress progress); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string& boundary, const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items) const; + + std::string adjust_host_string(const std::string& host) const; + + virtual bool process_socket(const Socket& socket, + std::function callback); + virtual bool is_ssl() const; + }; + + class Client + { + public: + // Universal interface + explicit Client(const std::string& scheme_host_port); + + explicit Client(const std::string& scheme_host_port, + const std::string& client_cert_path, + const std::string& client_key_path); + + // HTTP only interface + explicit Client(const std::string& host, int port); + + explicit Client(const std::string& host, int port, + const std::string& client_cert_path, + const std::string& client_key_path); + + Client(Client&&) = default; + Client& operator=(Client&&) = default; + + ~Client(); + + bool is_valid() const; + + Result Get(const std::string& path); + Result Get(const std::string& path, const Headers& headers); + Result Get(const std::string& path, Progress progress); + Result Get(const std::string& path, const Headers& headers, + Progress progress); + Result Get(const std::string& path, ContentReceiver content_receiver); + Result Get(const std::string& path, const Headers& headers, + ContentReceiver content_receiver); + Result Get(const std::string& path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string& path, const Headers& headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string& path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string& path, const Headers& headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string& path, const Headers& headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string& path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string& path, const Params& params, + const Headers& headers, Progress progress = nullptr); + Result Get(const std::string& path, const Params& params, + const Headers& headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string& path, const Params& params, + const Headers& headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string& path); + Result Head(const std::string& path, const Headers& headers); + + Result Post(const std::string& path); + Result Post(const std::string& path, const Headers& headers); + Result Post(const std::string& path, const char* body, size_t content_length, + const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, const char* body, + size_t content_length, const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, const char* body, + size_t content_length, const std::string& content_type, + Progress progress); + Result Post(const std::string& path, const std::string& body, + const std::string& content_type); + Result Post(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress); + Result Post(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type, + Progress progress); + Result Post(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type); + Result Post(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, + size_t content_length, ContentProvider content_provider, + const std::string& content_type); + Result Post(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Post(const std::string& path, const Params& params); + Result Post(const std::string& path, const Headers& headers, + const Params& params); + Result Post(const std::string& path, const Headers& headers, + const Params& params, Progress progress); + Result Post(const std::string& path, const MultipartFormDataItems& items); + Result Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items); + Result Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, const std::string& boundary); + Result Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items); + + Result Put(const std::string& path); + Result Put(const std::string& path, const char* body, size_t content_length, + const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, const char* body, + size_t content_length, const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, const char* body, + size_t content_length, const std::string& content_type, + Progress progress); + Result Put(const std::string& path, const std::string& body, + const std::string& content_type); + Result Put(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress); + Result Put(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type, + Progress progress); + Result Put(const std::string& path, size_t content_length, + ContentProvider content_provider, const std::string& content_type); + Result Put(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, + size_t content_length, ContentProvider content_provider, + const std::string& content_type); + Result Put(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Put(const std::string& path, const Params& params); + Result Put(const std::string& path, const Headers& headers, + const Params& params); + Result Put(const std::string& path, const Headers& headers, + const Params& params, Progress progress); + Result Put(const std::string& path, const MultipartFormDataItems& items); + Result Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items); + Result Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, const std::string& boundary); + Result Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items); + + Result Patch(const std::string& path); + Result Patch(const std::string& path, const char* body, size_t content_length, + const std::string& content_type); + Result Patch(const std::string& path, const char* body, size_t content_length, + const std::string& content_type, Progress progress); + Result Patch(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type); + Result Patch(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, Progress progress); + Result Patch(const std::string& path, const std::string& body, + const std::string& content_type); + Result Patch(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress); + Result Patch(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type); + Result Patch(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type, + Progress progress); + Result Patch(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type); + Result Patch(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + Result Patch(const std::string& path, const Headers& headers, + size_t content_length, ContentProvider content_provider, + const std::string& content_type); + Result Patch(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type); + + Result Delete(const std::string& path); + Result Delete(const std::string& path, const Headers& headers); + Result Delete(const std::string& path, const char* body, + size_t content_length, const std::string& content_type); + Result Delete(const std::string& path, const char* body, + size_t content_length, const std::string& content_type, + Progress progress); + Result Delete(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type); + Result Delete(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, Progress progress); + Result Delete(const std::string& path, const std::string& body, + const std::string& content_type); + Result Delete(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress); + Result Delete(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type); + Result Delete(const std::string& path, const Headers& headers, + const std::string& body, const std::string& content_type, + Progress progress); + + Result Options(const std::string& path); + Result Options(const std::string& path, const Headers& headers); + + bool send(Request& req, Response& res, Error& error); + Result send(const Request& req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const& writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration& duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration& duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration& duration); + + void set_basic_auth(const std::string& username, const std::string& password); + void set_bearer_token_auth(const std::string& token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string& intf); + + void set_proxy(const std::string& host, int port); + void set_proxy_basic_auth(const std::string& username, + const std::string& password); + void set_proxy_bearer_token_auth(const std::string& token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier(std::function verifier); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + + private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif + }; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + + void update_certs(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient final : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password = std::string()); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key, + const std::string &private_key_password = std::string()); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + + /* + * Implementation of template methods. + */ + + namespace detail + { + template + inline void duration_to_sec_and_usec(const T& duration, U callback) + { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); + } + + inline uint64_t get_header_value_u64(const Headers& headers, + const std::string& key, uint64_t def, + size_t id) + { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) + { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; + } + } // namespace detail + + inline uint64_t Request::get_header_value_u64(const std::string& key, + uint64_t def, size_t id) const + { + return detail::get_header_value_u64(headers, key, def, id); + } + + inline uint64_t Response::get_header_value_u64(const std::string& key, + uint64_t def, size_t id) const + { + return detail::get_header_value_u64(headers, key, def, id); + } + + inline void default_socket_options(socket_t sock) + { + int opt = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&opt), sizeof(opt)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&opt), sizeof(opt)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&opt), sizeof(opt)); +#endif +#endif + } + + inline const char* status_message(int status) + { + switch (status) + { + case StatusCode::Continue_100: + return "Continue"; + case StatusCode::SwitchingProtocol_101: + return "Switching Protocol"; + case StatusCode::Processing_102: + return "Processing"; + case StatusCode::EarlyHints_103: + return "Early Hints"; + case StatusCode::OK_200: + return "OK"; + case StatusCode::Created_201: + return "Created"; + case StatusCode::Accepted_202: + return "Accepted"; + case StatusCode::NonAuthoritativeInformation_203: + return "Non-Authoritative Information"; + case StatusCode::NoContent_204: + return "No Content"; + case StatusCode::ResetContent_205: + return "Reset Content"; + case StatusCode::PartialContent_206: + return "Partial Content"; + case StatusCode::MultiStatus_207: + return "Multi-Status"; + case StatusCode::AlreadyReported_208: + return "Already Reported"; + case StatusCode::IMUsed_226: + return "IM Used"; + case StatusCode::MultipleChoices_300: + return "Multiple Choices"; + case StatusCode::MovedPermanently_301: + return "Moved Permanently"; + case StatusCode::Found_302: + return "Found"; + case StatusCode::SeeOther_303: + return "See Other"; + case StatusCode::NotModified_304: + return "Not Modified"; + case StatusCode::UseProxy_305: + return "Use Proxy"; + case StatusCode::unused_306: + return "unused"; + case StatusCode::TemporaryRedirect_307: + return "Temporary Redirect"; + case StatusCode::PermanentRedirect_308: + return "Permanent Redirect"; + case StatusCode::BadRequest_400: + return "Bad Request"; + case StatusCode::Unauthorized_401: + return "Unauthorized"; + case StatusCode::PaymentRequired_402: + return "Payment Required"; + case StatusCode::Forbidden_403: + return "Forbidden"; + case StatusCode::NotFound_404: + return "Not Found"; + case StatusCode::MethodNotAllowed_405: + return "Method Not Allowed"; + case StatusCode::NotAcceptable_406: + return "Not Acceptable"; + case StatusCode::ProxyAuthenticationRequired_407: + return "Proxy Authentication Required"; + case StatusCode::RequestTimeout_408: + return "Request Timeout"; + case StatusCode::Conflict_409: + return "Conflict"; + case StatusCode::Gone_410: + return "Gone"; + case StatusCode::LengthRequired_411: + return "Length Required"; + case StatusCode::PreconditionFailed_412: + return "Precondition Failed"; + case StatusCode::PayloadTooLarge_413: + return "Payload Too Large"; + case StatusCode::UriTooLong_414: + return "URI Too Long"; + case StatusCode::UnsupportedMediaType_415: + return "Unsupported Media Type"; + case StatusCode::RangeNotSatisfiable_416: + return "Range Not Satisfiable"; + case StatusCode::ExpectationFailed_417: + return "Expectation Failed"; + case StatusCode::ImATeapot_418: + return "I'm a teapot"; + case StatusCode::MisdirectedRequest_421: + return "Misdirected Request"; + case StatusCode::UnprocessableContent_422: + return "Unprocessable Content"; + case StatusCode::Locked_423: + return "Locked"; + case StatusCode::FailedDependency_424: + return "Failed Dependency"; + case StatusCode::TooEarly_425: + return "Too Early"; + case StatusCode::UpgradeRequired_426: + return "Upgrade Required"; + case StatusCode::PreconditionRequired_428: + return "Precondition Required"; + case StatusCode::TooManyRequests_429: + return "Too Many Requests"; + case StatusCode::RequestHeaderFieldsTooLarge_431: + return "Request Header Fields Too Large"; + case StatusCode::UnavailableForLegalReasons_451: + return "Unavailable For Legal Reasons"; + case StatusCode::NotImplemented_501: + return "Not Implemented"; + case StatusCode::BadGateway_502: + return "Bad Gateway"; + case StatusCode::ServiceUnavailable_503: + return "Service Unavailable"; + case StatusCode::GatewayTimeout_504: + return "Gateway Timeout"; + case StatusCode::HttpVersionNotSupported_505: + return "HTTP Version Not Supported"; + case StatusCode::VariantAlsoNegotiates_506: + return "Variant Also Negotiates"; + case StatusCode::InsufficientStorage_507: + return "Insufficient Storage"; + case StatusCode::LoopDetected_508: + return "Loop Detected"; + case StatusCode::NotExtended_510: + return "Not Extended"; + case StatusCode::NetworkAuthenticationRequired_511: + return "Network Authentication Required"; + + default: + case StatusCode::InternalServerError_500: + return "Internal Server Error"; + } + } + + inline std::string get_bearer_token_auth(const Request& req) + { + if (req.has_header("Authorization")) + { + static std::string BearerHeaderPrefix = "Bearer "; + return req.get_header_value("Authorization") + .substr(BearerHeaderPrefix.length()); + } + return ""; + } + + template + inline Server& + Server::set_read_timeout(const std::chrono::duration& duration) + { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; + } + + template + inline Server& + Server::set_write_timeout(const std::chrono::duration& duration) + { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; + } + + template + inline Server& + Server::set_idle_interval(const std::chrono::duration& duration) + { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; + } + + inline std::string to_string(const Error error) + { + switch (error) + { + case Error::Success: + return "Success (no error)"; + case Error::Connection: + return "Could not establish connection"; + case Error::BindIPAddress: + return "Failed to bind IP address"; + case Error::Read: + return "Failed to read connection"; + case Error::Write: + return "Failed to write connection"; + case Error::ExceedRedirectCount: + return "Maximum redirect count exceeded"; + case Error::Canceled: + return "Connection handling canceled"; + case Error::SSLConnection: + return "SSL connection failed"; + case Error::SSLLoadingCerts: + return "SSL certificate loading failed"; + case Error::SSLServerVerification: + return "SSL server verification failed"; + case Error::SSLServerHostnameVerification: + return "SSL server hostname verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: + return "Compression failed"; + case Error::ConnectionTimeout: + return "Connection timed out"; + case Error::ProxyConnection: + return "Proxy connection failed"; + case Error::Unknown: + return "Unknown"; + default: + break; + } + + return "Invalid"; + } + + inline std::ostream& operator<<(std::ostream& os, const Error& obj) + { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; + } + + inline uint64_t Result::get_request_header_value_u64(const std::string& key, + uint64_t def, + size_t id) const + { + return detail::get_header_value_u64(request_headers_, key, def, id); + } + + template + inline void ClientImpl::set_connection_timeout( + const std::chrono::duration& duration) + { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) + { + set_connection_timeout(sec, usec); + }); + } + + template + inline void ClientImpl::set_read_timeout( + const std::chrono::duration& duration) + { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + } + + template + inline void ClientImpl::set_write_timeout( + const std::chrono::duration& duration) + { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + } + + template + inline void Client::set_connection_timeout( + const std::chrono::duration& duration) + { + cli_->set_connection_timeout(duration); + } + + template + inline void + Client::set_read_timeout(const std::chrono::duration& duration) + { + cli_->set_read_timeout(duration); + } + + template + inline void + Client::set_write_timeout(const std::chrono::duration& duration) + { + cli_->set_write_timeout(duration); + } + + /* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + + std::string hosted_at(const std::string& hostname); + + void hosted_at(const std::string& hostname, std::vector& addrs); + + std::string append_query_params(const std::string& path, const Params& params); + + std::pair make_range_header(const Ranges& ranges); + + std::pair + make_basic_authentication_header(const std::string& username, + const std::string& password, + bool is_proxy = false); + + namespace detail + { +#if defined(_WIN32) + inline std::wstring u8string_to_wstring(const char* s) + { + std::wstring ws; + auto len = static_cast(strlen(s)); + auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0); + if (wlen > 0) + { + ws.resize(wlen); + wlen = ::MultiByteToWideChar( + CP_UTF8, 0, s, len, + const_cast(reinterpret_cast(ws.data())), wlen); + if (wlen != static_cast(ws.size())) { ws.clear(); } + } + return ws; + } +#endif + + struct FileStat + { + FileStat(const std::string& path); + bool is_file() const; + bool is_dir() const; + + private: +#if defined(_WIN32) + struct _stat st_; +#else + struct stat st_; +#endif + int ret_ = -1; + }; + + std::string encode_query_param(const std::string& value); + + std::string decode_url(const std::string& s, bool convert_plus_to_space); + + void read_file(const std::string& path, std::string& out); + + std::string trim_copy(const std::string& s); + + void divide( + const char* data, std::size_t size, char d, + std::function + fn); + + void divide( + const std::string& str, char d, + std::function + fn); + + void split(const char* b, const char* e, char d, + std::function fn); + + void split(const char* b, const char* e, char d, size_t m, + std::function fn); + + bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + + socket_t create_client_socket(const std::string& host, const std::string& ip, + int port, int address_family, bool tcp_nodelay, + bool ipv6_v6only, SocketOptions socket_options, + time_t connection_timeout_sec, + time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + const std::string& intf, Error& error); + + const char* get_header_value(const Headers& headers, const std::string& key, + const char* def, size_t id); + + std::string params_to_query_str(const Params& params); + + void parse_query_text(const char* data, std::size_t size, Params& params); + + void parse_query_text(const std::string& s, Params& params); + + bool parse_multipart_boundary(const std::string& content_type, + std::string& boundary); + + bool parse_range_header(const std::string& s, Ranges& ranges); + + int close_socket(socket_t sock); + + ssize_t send_socket(socket_t sock, const void* ptr, size_t size, int flags); + + ssize_t read_socket(socket_t sock, void* ptr, size_t size, int flags); + + enum class EncodingType { None = 0, Gzip, Brotli }; + + EncodingType encoding_type(const Request& req, const Response& res); + + class BufferStream final : public Stream + { + public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char* ptr, size_t size) override; + ssize_t write(const char* ptr, size_t size) override; + void get_remote_ip_and_port(std::string& ip, int& port) const override; + void get_local_ip_and_port(std::string& ip, int& port) const override; + socket_t socket() const override; + + const std::string& get_buffer() const; + + private: + std::string buffer; + size_t position = 0; + }; + + class compressor + { + public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char* data, size_t data_length, bool last, + Callback callback) = 0; + }; + + class decompressor + { + public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char* data, size_t data_length, + Callback callback) = 0; + }; + + class nocompressor final : public compressor + { + public: + ~nocompressor() override = default; + + bool compress(const char* data, size_t data_length, bool /*last*/, + Callback callback) override; + }; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor final : public compressor { +public: + gzip_compressor(); + ~gzip_compressor() override; + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor final : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor() override; + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor final : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor final : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + + // NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` + // to store data. The call can set memory on stack for performance. + class stream_line_reader + { + public: + stream_line_reader(Stream& strm, char* fixed_buffer, + size_t fixed_buffer_size); + const char* ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + + private: + void append(char c); + + Stream& strm_; + char* fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; + }; + + class mmap + { + public: + mmap(const char* path); + ~mmap(); + + bool open(const char* path); + void close(); + + bool is_open() const; + size_t size() const; + const char* data() const; + + private: +#if defined(_WIN32) + HANDLE hFile_ = NULL; + HANDLE hMapping_ = NULL; +#else + int fd_ = -1; +#endif + size_t size_ = 0; + void* addr_ = nullptr; + bool is_open_empty_file = false; + }; + } // namespace detail + + // ---------------------------------------------------------------------------- + + /* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + + namespace detail + { + inline bool is_hex(char c, int& v) + { + if (0x20 <= c && isdigit(c)) + { + v = c - '0'; + return true; + } + else if ('A' <= c && c <= 'F') + { + v = c - 'A' + 10; + return true; + } + else if ('a' <= c && c <= 'f') + { + v = c - 'a' + 10; + return true; + } + return false; + } + + inline bool from_hex_to_i(const std::string& s, size_t i, size_t cnt, + int& val) + { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) + { + if (!s[i]) { return false; } + auto v = 0; + if (is_hex(s[i], v)) + { + val = val * 16 + v; + } + else + { + return false; + } + } + return true; + } + + inline std::string from_i_to_hex(size_t n) + { + static const auto charset = "0123456789abcdef"; + std::string ret; + do + { + ret = charset[n & 15] + ret; + n >>= 4; + } + while (n > 0); + return ret; + } + + inline size_t to_utf8(int code, char* buff) + { + if (code < 0x0080) + { + buff[0] = static_cast(code & 0x7F); + return 1; + } + else if (code < 0x0800) + { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } + else if (code < 0xD800) + { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } + else if (code < 0xE000) + { + // D800 - DFFF is invalid... + return 0; + } + else if (code < 0x10000) + { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } + else if (code < 0x110000) + { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; + } + + // NOTE: This code came up with the following stackoverflow post: + // https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c + inline std::string base64_encode(const std::string& in) + { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + auto val = 0; + auto valb = -6; + + for (auto c : in) + { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) + { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) + { + out.push_back('='); + } + + return out; + } + + inline bool is_valid_path(const std::string& path) + { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') + { + i++; + } + + while (i < path.size()) + { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') + { + if (path[i] == '\0') + { + return false; + } + else if (path[i] == '\\') + { + return false; + } + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) + { + ; + } + else if (!path.compare(beg, len, "..")) + { + if (level == 0) { return false; } + level--; + } + else + { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') + { + i++; + } + } + + return true; + } + + inline FileStat::FileStat(const std::string& path) + { +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path.c_str()); + ret_ = _wstat(wpath.c_str(), &st_); +#else + ret_ = stat(path.c_str(), &st_); +#endif + } + + inline bool FileStat::is_file() const + { + return ret_ >= 0 && S_ISREG(st_.st_mode); + } + + inline bool FileStat::is_dir() const + { + return ret_ >= 0 && S_ISDIR(st_.st_mode); + } + + inline std::string encode_query_param(const std::string& value) + { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) + { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') + { + escaped << c; + } + else + { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); + } + + inline std::string encode_url(const std::string& s) + { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) + { + switch (s[i]) + { + case ' ': + result += "%20"; + break; + case '+': + result += "%2B"; + break; + case '\r': + result += "%0D"; + break; + case '\n': + result += "%0A"; + break; + case '\'': + result += "%27"; + break; + case ',': + result += "%2C"; + break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': + result += "%3B"; + break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) + { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } + else + { + result += s[i]; + } + break; + } + } + + return result; + } + + inline std::string decode_url(const std::string& s, + bool convert_plus_to_space) + { + std::string result; + + for (size_t i = 0; i < s.size(); i++) + { + if (s[i] == '%' && i + 1 < s.size()) + { + if (s[i + 1] == 'u') + { + auto val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) + { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } + else + { + result += s[i]; + } + } + else + { + auto val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) + { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } + else + { + result += s[i]; + } + } + } + else if (convert_plus_to_space && s[i] == '+') + { + result += ' '; + } + else + { + result += s[i]; + } + } + + return result; + } + + inline void read_file(const std::string& path, std::string& out) + { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); + } + + inline std::string file_extension(const std::string& path) + { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); + } + + inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + + inline std::pair trim(const char* b, const char* e, size_t left, + size_t right) + { + while (b + left < e && is_space_or_tab(b[left])) + { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) + { + right--; + } + return std::make_pair(left, right); + } + + inline std::string trim_copy(const std::string& s) + { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); + } + + inline std::string trim_double_quotes_copy(const std::string& s) + { + if (s.length() >= 2 && s.front() == '"' && s.back() == '"') + { + return s.substr(1, s.size() - 2); + } + return s; + } + + inline void + divide(const char* data, std::size_t size, char d, + std::function + fn) + { + const auto it = std::find(data, data + size, d); + const auto found = static_cast(it != data + size); + const auto lhs_data = data; + const auto lhs_size = static_cast(it - data); + const auto rhs_data = it + found; + const auto rhs_size = size - lhs_size - found; + + fn(lhs_data, lhs_size, rhs_data, rhs_size); + } + + inline void + divide(const std::string& str, char d, + std::function + fn) + { + divide(str.data(), str.size(), d, std::move(fn)); + } + + inline void split(const char* b, const char* e, char d, + std::function fn) + { + return split(b, e, d, (std::numeric_limits::max)(), std::move(fn)); + } + + inline void split(const char* b, const char* e, char d, size_t m, + std::function fn) + { + size_t i = 0; + size_t beg = 0; + size_t count = 1; + + while (e ? (b + i < e) : (b[i] != '\0')) + { + if (b[i] == d && count < m) + { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + count++; + } + i++; + } + + if (i) + { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } + } + + inline stream_line_reader::stream_line_reader(Stream& strm, char* fixed_buffer, + size_t fixed_buffer_size) : + strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) + { + } + + inline const char* stream_line_reader::ptr() const + { + if (glowable_buffer_.empty()) + { + return fixed_buffer_; + } + else + { + return glowable_buffer_.data(); + } + } + + inline size_t stream_line_reader::size() const + { + if (glowable_buffer_.empty()) + { + return fixed_buffer_used_size_; + } + else + { + return glowable_buffer_.size(); + } + } + + inline bool stream_line_reader::end_with_crlf() const + { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; + } + + inline bool stream_line_reader::getline() + { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + +#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + char prev_byte = 0; +#endif + + for (size_t i = 0;; i++) + { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) + { + return false; + } + else if (n == 0) + { + if (i == 0) + { + return false; + } + else + { + break; + } + } + + append(byte); + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + if (byte == '\n') { break; } +#else + if (prev_byte == '\r' && byte == '\n') { break; } + prev_byte = byte; +#endif + } + + return true; + } + + inline void stream_line_reader::append(char c) + { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) + { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } + else + { + if (glowable_buffer_.empty()) + { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } + } + + inline mmap::mmap(const char* path) { open(path); } + + inline mmap::~mmap() { close(); } + + inline bool mmap::open(const char* path) + { + close(); + +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path); + if (wpath.empty()) { return false; } + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, + OPEN_EXISTING, NULL); +#else + hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#endif + + if (hFile_ == INVALID_HANDLE_VALUE) { return false; } + + LARGE_INTEGER size{}; + if (!::GetFileSizeEx(hFile_, &size)) { return false; } + // If the following line doesn't compile due to QuadPart, update Windows SDK. + // See: + // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721 + if (static_cast(size.QuadPart) > + (std::numeric_limits::max)()) + { + // `size_t` might be 32-bits, on 32-bits Windows. + return false; + } + size_ = static_cast(size.QuadPart); + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + hMapping_ = + ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL); +#else + hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL); +#endif + + // Special treatment for an empty file... + if (hMapping_ == NULL && size_ == 0) + { + close(); + is_open_empty_file = true; + return true; + } + + if (hMapping_ == NULL) + { + close(); + return false; + } + +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 + addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0); +#else + addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0); +#endif + + if (addr_ == nullptr) + { + close(); + return false; + } +#else + fd_ = ::open(path, O_RDONLY); + if (fd_ == -1) { return false; } + + struct stat sb; + if (fstat(fd_, &sb) == -1) { + close(); + return false; + } + size_ = static_cast(sb.st_size); + + addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0); + + // Special treatment for an empty file... + if (addr_ == MAP_FAILED && size_ == 0) { + close(); + is_open_empty_file = true; + return false; + } +#endif + + return true; + } + + inline bool mmap::is_open() const + { + return is_open_empty_file ? true : addr_ != nullptr; + } + + inline size_t mmap::size() const { return size_; } + + inline const char* mmap::data() const + { + return is_open_empty_file ? "" : static_cast(addr_); + } + + inline void mmap::close() + { +#if defined(_WIN32) + if (addr_) + { + ::UnmapViewOfFile(addr_); + addr_ = nullptr; + } + + if (hMapping_) + { + ::CloseHandle(hMapping_); + hMapping_ = NULL; + } + + if (hFile_ != INVALID_HANDLE_VALUE) + { + ::CloseHandle(hFile_); + hFile_ = INVALID_HANDLE_VALUE; + } + + is_open_empty_file = false; +#else + if (addr_ != nullptr) { + munmap(addr_, size_); + addr_ = nullptr; + } + + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } +#endif + size_ = 0; + } + + inline int close_socket(socket_t sock) + { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif + } + + template + inline ssize_t handle_EINTR(T fn) + { + ssize_t res = 0; + while (true) + { + res = fn(); + if (res < 0 && errno == EINTR) + { + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } + break; + } + return res; + } + + inline ssize_t read_socket(socket_t sock, void* ptr, size_t size, int flags) + { + return handle_EINTR([&]() + { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); + } + + inline ssize_t send_socket(socket_t sock, const void* ptr, size_t size, + int flags) + { + return handle_EINTR([&]() + { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); + } + + inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) + { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return -1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() + { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif + } + + inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) + { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return -1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() + { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif + } + + inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) + { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() + { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) + { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif + } + + inline bool is_socket_alive(socket_t sock) + { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) + { + return true; + } + else if (val < 0 && errno == EBADF) + { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; + } + + class SocketStream final : public Stream + { + public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char* ptr, size_t size) override; + ssize_t write(const char* ptr, size_t size) override; + void get_remote_ip_and_port(std::string& ip, int& port) const override; + void get_local_ip_and_port(std::string& ip, int& port) const override; + socket_t socket() const override; + + private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024l * 4; + }; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream final : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + + inline bool keep_alive(const std::atomic& svr_sock, socket_t sock, + time_t keep_alive_timeout_sec) + { + using namespace std::chrono; + + const auto interval_usec = + CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND; + + // Avoid expensive `steady_clock::now()` call for the first time + if (select_read(sock, 0, interval_usec) > 0) { return true; } + + const auto start = steady_clock::now() - microseconds{interval_usec}; + const auto timeout = seconds{keep_alive_timeout_sec}; + + while (true) + { + if (svr_sock == INVALID_SOCKET) + { + break; // Server socket is closed + } + + auto val = select_read(sock, 0, interval_usec); + if (val < 0) + { + break; // Ssocket error + } + else if (val == 0) + { + if (steady_clock::now() - start > timeout) + { + break; // Timeout + } + } + else + { + return true; // Ready for read + } + } + + return false; + } + + template + inline bool + process_server_socket_core(const std::atomic& svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) + { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) + { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; + } + + template + inline bool + process_server_socket(const std::atomic& svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) + { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool& connection_closed) + { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); + } + + inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback) + { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); + } + + inline int shutdown_socket(socket_t sock) + { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif + } + + inline std::string escape_abstract_namespace_unix_domain(const std::string& s) + { + if (s.size() > 1 && s[0] == '\0') + { + auto ret = s; + ret[0] = '@'; + return ret; + } + return s; + } + + inline std::string + unescape_abstract_namespace_unix_domain(const std::string& s) + { + if (s.size() > 1 && s[0] == '@') + { + auto ret = s; + ret[0] = '\0'; + return ret; + } + return s; + } + + template + socket_t create_socket(const std::string& host, const std::string& ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + bool ipv6_v6only, SocketOptions socket_options, + BindOrConnect bind_or_connect) + { + // Get address info + const char* node = nullptr; + struct addrinfo hints; + struct addrinfo* result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_IP; + + if (!ip.empty()) + { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } + else + { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; } + +#ifdef SOCK_CLOEXEC + auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC, + hints.ai_protocol); +#else + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); +#endif + + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + auto unescaped_host = unescape_abstract_namespace_unix_domain(host); + std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + +#ifndef SOCK_CLOEXEC + fcntl(sock, F_SETFD, FD_CLOEXEC); +#endif + + if (socket_options) { socket_options(sock); } + + bool dummy; + if (!bind_or_connect(sock, hints, dummy)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) + { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) + { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) + { + sock = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + +#ifdef SOCK_CLOEXEC + auto sock = + socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol); +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + +#endif + if (sock == INVALID_SOCKET) { continue; } + +#if !defined _WIN32 && !defined SOCK_CLOEXEC + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) + { + auto opt = 1; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&opt), sizeof(opt)); +#endif + } + + if (rp->ai_family == AF_INET6) + { + auto opt = ipv6_v6only ? 1 : 0; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&opt), sizeof(opt)); +#endif + } + + if (socket_options) { socket_options(sock); } + + // bind or connect + auto quit = false; + if (bind_or_connect(sock, *rp, quit)) { return sock; } + + close_socket(sock); + + if (quit) { break; } + } + + return INVALID_SOCKET; + } + + inline void set_nonblocking(socket_t sock, bool nonblocking) + { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif + } + + inline bool is_connection_error() + { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif + } + + inline bool bind_ip_address(socket_t sock, const std::string& host) + { + struct addrinfo hints; + struct addrinfo* result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) + { + const auto& ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) + { + ret = true; + break; + } + } + + return ret; + } + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + auto se = detail::scope_exit([&] { freeifaddrs(ifap); }); + + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + return addr_candidate; +} +#endif + + inline socket_t create_client_socket( + const std::string& host, const std::string& ip, int port, + int address_family, bool tcp_nodelay, bool ipv6_v6only, + SocketOptions socket_options, time_t connection_timeout_sec, + time_t connection_timeout_usec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string& intf, Error& error) + { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only, + std::move(socket_options), + [&](socket_t sock2, struct addrinfo& ai, bool& quit) -> bool + { + if (!intf.empty()) + { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if)) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) + { + if (is_connection_error()) + { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) + { + if (error == Error::ConnectionTimeout) { quit = true; } + return false; + } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) + { + error = Error::Success; + } + else + { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; + } + + inline bool get_ip_and_port(const struct sockaddr_storage& addr, + socklen_t addr_len, std::string& ip, int& port) + { + if (addr.ss_family == AF_INET) + { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } + else if (addr.ss_family == AF_INET6) + { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } + else + { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) + { + return false; + } + + ip = ipstr.data(); + return true; + } + + inline void get_local_ip_and_port(socket_t sock, std::string& ip, int& port) + { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) + { + get_ip_and_port(addr, addr_len, ip, port); + } + } + + inline void get_remote_ip_and_port(socket_t sock, std::string& ip, int& port) + { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) + { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } + } + + inline constexpr unsigned int str2tag_core(const char* s, size_t l, + unsigned int h) + { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); + } + + inline unsigned int str2tag(const std::string& s) + { + return str2tag_core(s.data(), s.size(), 0); + } + + namespace udl + { + inline constexpr unsigned int operator""_t(const char* s, size_t l) + { + return str2tag_core(s, l, 0); + } + } // namespace udl + + inline std::string + find_content_type(const std::string& path, + const std::map& user_data, + const std::string& default_content_type) + { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second; } + + using udl::operator""_t; + + switch (str2tag(ext)) + { + default: + return default_content_type; + + case "css"_t: + return "text/css"; + case "csv"_t: + return "text/csv"; + case "htm"_t: + case "html"_t: + return "text/html"; + case "js"_t: + case "mjs"_t: + return "text/javascript"; + case "txt"_t: + return "text/plain"; + case "vtt"_t: + return "text/vtt"; + + case "apng"_t: + return "image/apng"; + case "avif"_t: + return "image/avif"; + case "bmp"_t: + return "image/bmp"; + case "gif"_t: + return "image/gif"; + case "png"_t: + return "image/png"; + case "svg"_t: + return "image/svg+xml"; + case "webp"_t: + return "image/webp"; + case "ico"_t: + return "image/x-icon"; + case "tif"_t: + return "image/tiff"; + case "tiff"_t: + return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: + return "image/jpeg"; + + case "mp4"_t: + return "video/mp4"; + case "mpeg"_t: + return "video/mpeg"; + case "webm"_t: + return "video/webm"; + + case "mp3"_t: + return "audio/mp3"; + case "mpga"_t: + return "audio/mpeg"; + case "weba"_t: + return "audio/webm"; + case "wav"_t: + return "audio/wave"; + + case "otf"_t: + return "font/otf"; + case "ttf"_t: + return "font/ttf"; + case "woff"_t: + return "font/woff"; + case "woff2"_t: + return "font/woff2"; + + case "7z"_t: + return "application/x-7z-compressed"; + case "atom"_t: + return "application/atom+xml"; + case "pdf"_t: + return "application/pdf"; + case "json"_t: + return "application/json"; + case "rss"_t: + return "application/rss+xml"; + case "tar"_t: + return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: + return "application/xhtml+xml"; + case "xslt"_t: + return "application/xslt+xml"; + case "xml"_t: + return "application/xml"; + case "gz"_t: + return "application/gzip"; + case "zip"_t: + return "application/zip"; + case "wasm"_t: + return "application/wasm"; + } + } + + inline bool can_compress_content_type(const std::string& content_type) + { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) + { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: + return true; + + case "text/event-stream"_t: + return false; + + default: + return !content_type.rfind("text/", 0); + } + } + + inline EncodingType encoding_type(const Request& req, const Response& res) + { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto& s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; + } + + inline bool nocompressor::compress(const char* data, size_t data_length, + bool /*last*/, Callback callback) + { + if (!data_length) { return true; } + return callback(data, data_length); + } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + auto ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + auto ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0 && ret == Z_OK) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) { return false; } + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + auto next_in = reinterpret_cast(data); + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + + inline bool has_header(const Headers& headers, const std::string& key) + { + return headers.find(key) != headers.end(); + } + + inline const char* get_header_value(const Headers& headers, + const std::string& key, const char* def, + size_t id) + { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; + } + + template + inline bool parse_header(const char* beg, const char* end, T fn) + { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) + { + end--; + } + + auto p = beg; + while (p < end && *p != ':') + { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) + { + p++; + } + + if (p <= end) + { + auto key_len = key_end - beg; + if (!key_len) { return false; } + + auto key = std::string(beg, key_end); + auto val = case_ignore::equal(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + + // NOTE: From RFC 9110: + // Field values containing CR, LF, or NUL characters are + // invalid and dangerous, due to the varying ways that + // implementations might parse and interpret those + // characters; a recipient of CR, LF, or NUL within a field + // value MUST either reject the message or replace each of + // those characters with SP before further processing or + // forwarding of that message. + static const std::string CR_LF_NUL("\r\n\0", 3); + if (val.find_first_of(CR_LF_NUL) != std::string::npos) { return false; } + + fn(key, val); + return true; + } + + return false; + } + + inline bool read_headers(Stream& strm, Headers& headers) + { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) + { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) + { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } + } + else + { +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; +#else + continue; // Skip invalid line. +#endif + } + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + if (!parse_header(line_reader.ptr(), end, + [&](const std::string& key, std::string& val) + { + headers.emplace(key, val); + })) + { + return false; + } + } + + return true; + } + + inline bool read_content_with_length(Stream& strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) + { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) + { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) + { + if (!progress(r, len)) { return false; } + } + } + + return true; + } + + inline void skip_content_with_length(Stream& strm, uint64_t len) + { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) + { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } + } + + inline bool read_content_without_length(Stream& strm, + ContentReceiverWithProgress out) + { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) + { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n <= 0) { return true; } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; + } + + template + inline bool read_content_chunked(Stream& strm, T& x, + ContentReceiverWithProgress out) + { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) + { + char* end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) + { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentiones "The chunked + // transfer coding is complete when a chunk with a chunk-size of zero is + // received, possibly followed by a trailer section, and finally terminated by + // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 + // + // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section + // does't care for the existence of the final CRLF. In other words, it seems + // to be ok whether the final CRLF exists or not in the chunked data. + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 + // + // According to the reference code in RFC 9112, cpp-htpplib now allows + // chuncked transfer coding data without the final CRLF. + if (!line_reader.getline()) { return true; } + + while (strcmp(line_reader.ptr(), "\r\n") != 0) + { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](const std::string& key, const std::string& val) + { + x.headers.emplace(key, val); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; + } + + inline bool is_chunked_transfer_encoding(const Headers& headers) + { + return case_ignore::equal( + get_header_value(headers, "Transfer-Encoding", "", 0), "chunked"); + } + + template + bool prepare_content_receiver(T& x, int& status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) + { + if (decompress) + { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") + { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } + else if (encoding.find("br") != std::string::npos) + { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } + + if (decompressor) + { + if (decompressor->is_valid()) + { + ContentReceiverWithProgress out = [&](const char* buf, size_t n, + uint64_t off, uint64_t len) + { + return decompressor->decompress(buf, n, + [&](const char* buf2, size_t n2) + { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } + else + { + status = StatusCode::InternalServerError_500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char* buf, size_t n, uint64_t off, + uint64_t len) + { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); + } + + template + bool read_content(Stream& strm, T& x, size_t payload_max_length, int& status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) + { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress& out) + { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) + { + ret = read_content_chunked(strm, x, out); + } + else if (!has_header(x.headers, "Content-Length")) + { + ret = read_content_without_length(strm, out); + } + else + { + auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0); + if (len > payload_max_length) + { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } + else if (len > 0) + { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) + { + status = exceed_payload_max_length + ? StatusCode::PayloadTooLarge_413 + : StatusCode::BadRequest_400; + } + return ret; + }); + } + + inline ssize_t write_request_line(Stream& strm, const std::string& method, + const std::string& path) + { + std::string s = method; + s += " "; + s += path; + s += " HTTP/1.1\r\n"; + return strm.write(s.data(), s.size()); + } + + inline ssize_t write_response_line(Stream& strm, int status) + { + std::string s = "HTTP/1.1 "; + s += std::to_string(status); + s += " "; + s += httplib::status_message(status); + s += "\r\n"; + return strm.write(s.data(), s.size()); + } + + inline ssize_t write_headers(Stream& strm, const Headers& headers) + { + ssize_t write_len = 0; + for (const auto& x : headers) + { + std::string s; + s = x.first; + s += ": "; + s += x.second; + s += "\r\n"; + + auto len = strm.write(s.data(), s.size()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; + } + + inline bool write_data(Stream& strm, const char* d, size_t l) + { + size_t offset = 0; + while (offset < l) + { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; + } + + template + inline bool write_content(Stream& strm, const ContentProvider& content_provider, + size_t offset, size_t length, T is_shutting_down, + Error& error) + { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char* d, size_t l) -> bool + { + if (ok) + { + if (strm.is_writable() && write_data(strm, d, l)) + { + offset += l; + } + else + { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + while (offset < end_offset && !is_shutting_down()) + { + if (!strm.is_writable()) + { + error = Error::Write; + return false; + } + else if (!content_provider(offset, end_offset - offset, data_sink)) + { + error = Error::Canceled; + return false; + } + else if (!ok) + { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; + } + + template + inline bool write_content(Stream& strm, const ContentProvider& content_provider, + size_t offset, size_t length, + const T& is_shutting_down) + { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); + } + + template + inline bool + write_content_without_length(Stream& strm, + const ContentProvider& content_provider, + const T& is_shutting_down) + { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char* d, size_t l) -> bool + { + if (ok) + { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) + { + if (!strm.is_writable()) + { + return false; + } + else if (!content_provider(offset, 0, data_sink)) + { + return false; + } + else if (!ok) + { + return false; + } + } + return true; + } + + template + inline bool + write_content_chunked(Stream& strm, const ContentProvider& content_provider, + const T& is_shutting_down, U& compressor, Error& error) + { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char* d, size_t l) -> bool + { + if (ok) + { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char* data, size_t data_len) + { + payload.append(data, data_len); + return true; + })) + { + if (!payload.empty()) + { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) + { + ok = false; + } + } + } + else + { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + auto done_with_trailer = [&](const Headers* trailer) + { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char* data, size_t data_len) + { + payload.append(data, data_len); + return true; + })) + { + ok = false; + return; + } + + if (!payload.empty()) + { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) + { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) + { + ok = false; + } + + // Trailer + if (trailer) + { + for (const auto& kv : *trailer) + { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) + { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers& trailer) + { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) + { + if (!strm.is_writable()) + { + error = Error::Write; + return false; + } + else if (!content_provider(offset, 0, data_sink)) + { + error = Error::Canceled; + return false; + } + else if (!ok) + { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; + } + + template + inline bool write_content_chunked(Stream& strm, + const ContentProvider& content_provider, + const T& is_shutting_down, U& compressor) + { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); + } + + template + inline bool redirect(T& cli, Request& req, Response& res, + const std::string& path, const std::string& location, + Error& error) + { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == StatusCode::SeeOther_303 && + (req.method != "GET" && req.method != "HEAD")) + { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) + { + req = new_req; + res = new_res; + + if (res.location.empty()) { res.location = location; } + } + return ret; + } + + inline std::string params_to_query_str(const Params& params) + { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) + { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; + } + + inline void parse_query_text(const char* data, std::size_t size, + Params& params) + { + std::set cache; + split(data, data + size, '&', [&](const char* b, const char* e) + { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(std::move(kv)); + + std::string key; + std::string val; + divide(b, static_cast(e - b), '=', + [&](const char* lhs_data, std::size_t lhs_size, const char* rhs_data, + std::size_t rhs_size) + { + key.assign(lhs_data, lhs_size); + val.assign(rhs_data, rhs_size); + }); + + if (!key.empty()) + { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); + } + + inline void parse_query_text(const std::string& s, Params& params) + { + parse_query_text(s.data(), s.size(), params); + } + + inline bool parse_multipart_boundary(const std::string& content_type, + std::string& boundary) + { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg)); + return !boundary.empty(); + } + + inline void parse_disposition_params(const std::string& s, Params& params) + { + std::set cache; + split(s.data(), s.data() + s.size(), ';', [&](const char* b, const char* e) + { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char* b2, const char* e2) + { + if (key.empty()) + { + key.assign(b2, e2); + } + else + { + val.assign(b2, e2); + } + }); + + if (!key.empty()) + { + params.emplace(trim_double_quotes_copy((key)), + trim_double_quotes_copy((val))); + } + }); + } + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else + inline bool parse_range_header(const std::string& s, Ranges& ranges) try + { +#endif + auto is_valid = [](const std::string& str) + { + return std::all_of(str.cbegin(), str.cend(), + [](unsigned char c) { return std::isdigit(c); }); + }; + + if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) + { + const auto pos = static_cast(6); + const auto len = static_cast(s.size() - 6); + auto all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char* b, const char* e) + { + if (!all_valid_ranges) { return; } + + const auto it = std::find(b, e, '-'); + if (it == e) + { + all_valid_ranges = false; + return; + } + + const auto lhs = std::string(b, it); + const auto rhs = std::string(it + 1, e); + if (!is_valid(lhs) || !is_valid(rhs)) + { + all_valid_ranges = false; + return; + } + + const auto first = + static_cast(lhs.empty() ? -1 : std::stoll(lhs)); + const auto last = + static_cast(rhs.empty() ? -1 : std::stoll(rhs)); + if ((first == -1 && last == -1) || + (first != -1 && last != -1 && first > last)) + { + all_valid_ranges = false; + return; + } + + ranges.emplace_back(first, last); + }); + return all_valid_ranges && !ranges.empty(); + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else + } + catch (...) { return false; } +#endif + + class MultipartFormDataParser + { + public: + MultipartFormDataParser() = default; + + void set_boundary(std::string&& boundary) + { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char* buf, size_t n, const ContentReceiver& content_callback, + const MultipartContentHeader& header_callback) + { + buf_append(buf, n); + + while (buf_size() > 0) + { + switch (state_) + { + case 0: + { + // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: + { + // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: + { + // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) + { + // Empty line + if (pos == 0) + { + if (!header_callback(file_)) + { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + const auto header = buf_head(pos); + + if (!parse_header(header.data(), header.data() + header.size(), + [&](const std::string&, const std::string&) + { + })) + { + is_valid_ = false; + return false; + } + + static const std::string header_content_type = "Content-Type:"; + + if (start_with_case_ignore(header, header_content_type)) + { + file_.content_type = + trim_copy(header.substr(header_content_type.size())); + } + else + { + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~", + std::regex_constants::icase); + + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) + { + Params params; + parse_disposition_params(m[1], params); + + auto it = params.find("name"); + if (it != params.end()) + { + file_.name = it->second; + } + else + { + is_valid_ = false; + return false; + } + + it = params.find("filename"); + if (it != params.end()) { file_.filename = it->second; } + + it = params.find("filename*"); + if (it != params.end()) + { + // Only allow UTF-8 enconnding... + static const std::regex re_rfc5987_encoding( + R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); + + std::smatch m2; + if (std::regex_match(it->second, m2, re_rfc5987_encoding)) + { + file_.filename = decode_url(m2[1], false); // override... + } + else + { + is_valid_ = false; + return false; + } + } + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: + { + // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) + { + if (!content_callback(buf_data(), pos)) + { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } + else + { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) + { + if (!content_callback(buf_data(), len)) + { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: + { + // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) + { + buf_erase(crlf_.size()); + state_ = 1; + } + else + { + if (dash_.size() > buf_size()) { return true; } + if (buf_start_with(dash_)) + { + buf_erase(dash_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } + else + { + return true; + } + } + break; + } + } + } + + return true; + } + + private: + void clear_file_info() + { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string& a, + const std::string& b) const + { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) + { + if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) + { + return false; + } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string& a, size_t spos, size_t epos, + const std::string& b) const + { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) + { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char* buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string& s) const + { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string& s) const + { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) + { + auto pos = off; + while (true) + { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char* data, size_t n) + { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) + { + for (size_t i = 0; i < remaining_size; i++) + { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) + { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; + }; + + inline std::string random_string(size_t length) + { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + static std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), + seed_gen()}; + + static std::mt19937 engine(seed_sequence); + + std::string result; + for (size_t i = 0; i < length; i++) + { + result += data[engine() % (sizeof(data) - 1)]; + } + return result; + } + + inline std::string make_multipart_data_boundary() + { + return "--cpp-httplib-multipart-data-" + detail::random_string(16); + } + + inline bool is_multipart_boundary_chars_valid(const std::string& boundary) + { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) + { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') + { + valid = false; + break; + } + } + return valid; + } + + template + inline std::string + serialize_multipart_formdata_item_begin(const T& item, + const std::string& boundary) + { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) + { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) + { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; + } + + inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + + inline std::string + serialize_multipart_formdata_finish(const std::string& boundary) + { + return "--" + boundary + "--\r\n"; + } + + inline std::string + serialize_multipart_formdata_get_content_type(const std::string& boundary) + { + return "multipart/form-data; boundary=" + boundary; + } + + inline std::string + serialize_multipart_formdata(const MultipartFormDataItems& items, + const std::string& boundary, bool finish = true) + { + std::string body; + + for (const auto& item : items) + { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) { body += serialize_multipart_formdata_finish(boundary); } + + return body; + } + + inline bool range_error(Request& req, Response& res) + { + if (!req.ranges.empty() && 200 <= res.status && res.status < 300) + { + ssize_t contant_len = static_cast( + res.content_length_ ? res.content_length_ : res.body.size()); + + ssize_t prev_first_pos = -1; + ssize_t prev_last_pos = -1; + size_t overwrapping_count = 0; + + // NOTE: The following Range check is based on '14.2. Range' in RFC 9110 + // 'HTTP Semantics' to avoid potential denial-of-service attacks. + // https://www.rfc-editor.org/rfc/rfc9110#section-14.2 + + // Too many ranges + if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; } + + for (auto& r : req.ranges) + { + auto& first_pos = r.first; + auto& last_pos = r.second; + + if (first_pos == -1 && last_pos == -1) + { + first_pos = 0; + last_pos = contant_len; + } + + if (first_pos == -1) + { + first_pos = contant_len - last_pos; + last_pos = contant_len - 1; + } + + if (last_pos == -1) { last_pos = contant_len - 1; } + + // Range must be within content length + if (!(0 <= first_pos && first_pos <= last_pos && + last_pos <= contant_len - 1)) + { + return true; + } + + // Ranges must be in ascending order + if (first_pos <= prev_first_pos) { return true; } + + // Request must not have more than two overlapping ranges + if (first_pos <= prev_last_pos) + { + overwrapping_count++; + if (overwrapping_count > 2) { return true; } + } + + prev_first_pos = (std::max)(prev_first_pos, first_pos); + prev_last_pos = (std::max)(prev_last_pos, last_pos); + } + } + + return false; + } + + inline std::pair + get_range_offset_and_length(Range r, size_t content_length) + { + assert(r.first != -1 && r.second != -1); + assert(0 <= r.first && r.first < static_cast(content_length)); + assert(r.first <= r.second && + r.second < static_cast(content_length)); + (void)(content_length); + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); + } + + inline std::string make_content_range_header_field( + const std::pair& offset_and_length, size_t content_length) + { + auto st = offset_and_length.first; + auto ed = st + offset_and_length.second - 1; + + std::string field = "bytes "; + field += std::to_string(st); + field += "-"; + field += std::to_string(ed); + field += "/"; + field += std::to_string(content_length); + return field; + } + + template + bool process_multipart_ranges_data(const Request& req, + const std::string& boundary, + const std::string& content_type, + size_t content_length, SToken stoken, + CToken ctoken, Content content) + { + for (size_t i = 0; i < req.ranges.size(); i++) + { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) + { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offset_and_length = + get_range_offset_and_length(req.ranges[i], content_length); + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset_and_length, content_length)); + ctoken("\r\n"); + ctoken("\r\n"); + + if (!content(offset_and_length.first, offset_and_length.second)) + { + return false; + } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--"); + + return true; + } + + inline void make_multipart_ranges_data(const Request& req, Response& res, + const std::string& boundary, + const std::string& content_type, + size_t content_length, + std::string& data) + { + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string& token) { data += token; }, + [&](const std::string& token) { data += token; }, + [&](size_t offset, size_t length) + { + assert(offset + length <= content_length); + data += res.body.substr(offset, length); + return true; + }); + } + + inline size_t get_multipart_ranges_data_length(const Request& req, + const std::string& boundary, + const std::string& content_type, + size_t content_length) + { + size_t data_length = 0; + + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string& token) { data_length += token.size(); }, + [&](const std::string& token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) + { + data_length += length; + return true; + }); + + return data_length; + } + + template + inline bool + write_multipart_ranges_data(Stream& strm, const Request& req, Response& res, + const std::string& boundary, + const std::string& content_type, + size_t content_length, const T& is_shutting_down) + { + return process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string& token) { strm.write(token); }, + [&](const std::string& token) { strm.write(token); }, + [&](size_t offset, size_t length) + { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); + } + + inline bool expect_content(const Request& req) + { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") + { + return true; + } + // TODO: check if Content-Length is set + return false; + } + + inline bool has_crlf(const std::string& s) + { + auto p = s.c_str(); + while (*p) + { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(hash[i]); + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (auto i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 + class WSInit + { + public: + WSInit() + { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() + { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; + }; + + static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + + inline bool parse_www_authenticate(const Response& res, + std::map& auth, + bool is_proxy) + { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) + { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) + { + auto type = s.substr(0, pos); + if (type == "Basic") + { + return false; + } + else if (type == "Digest") + { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) + { + const auto& m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; + } + + class ContentProviderAdapter + { + public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength&& content_provider) : + content_provider_(content_provider) + { + } + + bool operator()(size_t offset, size_t, DataSink& sink) + { + return content_provider_(offset, sink); + } + + private: + ContentProviderWithoutLength content_provider_; + }; + } // namespace detail + + inline std::string hosted_at(const std::string& hostname) + { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; + } + + inline void hosted_at(const std::string& hostname, + std::vector& addrs) + { + struct addrinfo hints; + struct addrinfo* result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) + { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); + + for (auto rp = result; rp; rp = rp->ai_next) + { + const auto& addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + auto dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) + { + addrs.push_back(ip); + } + } + } + + inline std::string append_query_params(const std::string& path, + const Params& params) + { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; + } + + // Header utilities + inline std::pair + make_range_header(const Ranges& ranges) + { + std::string field = "bytes="; + auto i = 0; + for (const auto& r : ranges) + { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); + } + + inline std::pair + make_basic_authentication_header(const std::string& username, + const std::string& password, bool is_proxy) + { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); + } + + inline std::pair + make_bearer_token_authentication_header(const std::string& token, + bool is_proxy = false) + { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); + } + + // Request implementation + inline bool Request::has_header(const std::string& key) const + { + return detail::has_header(headers, key); + } + + inline std::string Request::get_header_value(const std::string& key, + const char* def, size_t id) const + { + return detail::get_header_value(headers, key, def, id); + } + + inline size_t Request::get_header_value_count(const std::string& key) const + { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); + } + + inline void Request::set_header(const std::string& key, + const std::string& val) + { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) + { + headers.emplace(key, val); + } + } + + inline bool Request::has_param(const std::string& key) const + { + return params.find(key) != params.end(); + } + + inline std::string Request::get_param_value(const std::string& key, + size_t id) const + { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); + } + + inline size_t Request::get_param_value_count(const std::string& key) const + { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); + } + + inline bool Request::is_multipart_form_data() const + { + const auto& content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); + } + + inline bool Request::has_file(const std::string& key) const + { + return files.find(key) != files.end(); + } + + inline MultipartFormData Request::get_file_value(const std::string& key) const + { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); + } + + inline std::vector + Request::get_file_values(const std::string& key) const + { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) + { + values.push_back(it->second); + } + return values; + } + + // Response implementation + inline bool Response::has_header(const std::string& key) const + { + return headers.find(key) != headers.end(); + } + + inline std::string Response::get_header_value(const std::string& key, + const char* def, + size_t id) const + { + return detail::get_header_value(headers, key, def, id); + } + + inline size_t Response::get_header_value_count(const std::string& key) const + { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); + } + + inline void Response::set_header(const std::string& key, + const std::string& val) + { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) + { + headers.emplace(key, val); + } + } + + inline void Response::set_redirect(const std::string& url, int stat) + { + if (!detail::has_crlf(url)) + { + set_header("Location", url); + if (300 <= stat && stat < 400) + { + this->status = stat; + } + else + { + this->status = StatusCode::Found_302; + } + } + } + + inline void Response::set_content(const char* s, size_t n, + const std::string& content_type) + { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); + } + + inline void Response::set_content(const std::string& s, + const std::string& content_type) + { + set_content(s.data(), s.size(), content_type); + } + + inline void Response::set_content(std::string&& s, + const std::string& content_type) + { + body = std::move(s); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); + } + + inline void Response::set_content_provider( + size_t in_length, const std::string& content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) + { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; + } + + inline void Response::set_content_provider( + const std::string& content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) + { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; + } + + inline void Response::set_chunked_content_provider( + const std::string& content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) + { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = true; + } + + inline void Response::set_file_content(const std::string& path, + const std::string& content_type) + { + file_content_path_ = path; + file_content_content_type_ = content_type; + } + + inline void Response::set_file_content(const std::string& path) + { + file_content_path_ = path; + } + + // Result implementation + inline bool Result::has_request_header(const std::string& key) const + { + return request_headers_.find(key) != request_headers_.end(); + } + + inline std::string Result::get_request_header_value(const std::string& key, + const char* def, + size_t id) const + { + return detail::get_header_value(request_headers_, key, def, id); + } + + inline size_t + Result::get_request_header_value_count(const std::string& key) const + { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); + } + + // Stream implementation + inline ssize_t Stream::write(const char* ptr) + { + return write(ptr, strlen(ptr)); + } + + inline ssize_t Stream::write(const std::string& s) + { + return write(s.data(), s.size()); + } + + namespace detail + { + // Socket stream implementation + inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) : + sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) + { + } + + inline SocketStream::~SocketStream() = default; + + inline bool SocketStream::is_readable() const + { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + inline bool SocketStream::is_writable() const + { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); + } + + inline ssize_t SocketStream::read(char* ptr, size_t size) + { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) + { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) + { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } + else + { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) + { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) + { + return n; + } + else if (n <= static_cast(size)) + { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } + else + { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } + else + { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } + } + + inline ssize_t SocketStream::write(const char* ptr, size_t size) + { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); + } + + inline void SocketStream::get_remote_ip_and_port(std::string& ip, + int& port) const + { + return detail::get_remote_ip_and_port(sock_, ip, port); + } + + inline void SocketStream::get_local_ip_and_port(std::string& ip, + int& port) const + { + return detail::get_local_ip_and_port(sock_, ip, port); + } + + inline socket_t SocketStream::socket() const { return sock_; } + + // Buffer stream implementation + inline bool BufferStream::is_readable() const { return true; } + + inline bool BufferStream::is_writable() const { return true; } + + inline ssize_t BufferStream::read(char* ptr, size_t size) + { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); + } + + inline ssize_t BufferStream::write(const char* ptr, size_t size) + { + buffer.append(ptr, size); + return static_cast(size); + } + + inline void BufferStream::get_remote_ip_and_port(std::string& /*ip*/, + int& /*port*/) const + { + } + + inline void BufferStream::get_local_ip_and_port(std::string& /*ip*/, + int& /*port*/) const + { + } + + inline socket_t BufferStream::socket() const { return 0; } + + inline const std::string& BufferStream::get_buffer() const { return buffer; } + + inline PathParamsMatcher::PathParamsMatcher(const std::string& pattern) + { + static constexpr char marker[] = "/:"; + + // One past the last ending position of a path param substring + std::size_t last_param_end = 0; + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + // Needed to ensure that parameter names are unique during matcher + // construction + // If exceptions are disabled, only last duplicate path + // parameter will be set + std::unordered_set param_name_set; +#endif + + while (true) + { + const auto marker_pos = pattern.find( + marker, last_param_end == 0 ? last_param_end : last_param_end - 1); + if (marker_pos == std::string::npos) { break; } + + static_fragments_.push_back( + pattern.substr(last_param_end, marker_pos - last_param_end + 1)); + + const auto param_name_start = marker_pos + 2; + + auto sep_pos = pattern.find(separator, param_name_start); + if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } + + auto param_name = + pattern.substr(param_name_start, sep_pos - param_name_start); + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + if (param_name_set.find(param_name) != param_name_set.cend()) + { + std::string msg = "Encountered path parameter '" + param_name + + "' multiple times in route pattern '" + pattern + "'."; + throw std::invalid_argument(msg); + } +#endif + + param_names_.push_back(std::move(param_name)); + + last_param_end = sep_pos + 1; + } + + if (last_param_end < pattern.length()) + { + static_fragments_.push_back(pattern.substr(last_param_end)); + } + } + + inline bool PathParamsMatcher::match(Request& request) const + { + request.matches = std::smatch(); + request.path_params.clear(); + request.path_params.reserve(param_names_.size()); + + // One past the position at which the path matched the pattern last time + std::size_t starting_pos = 0; + for (size_t i = 0; i < static_fragments_.size(); ++i) + { + const auto& fragment = static_fragments_[i]; + + if (starting_pos + fragment.length() > request.path.length()) + { + return false; + } + + // Avoid unnecessary allocation by using strncmp instead of substr + + // comparison + if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(), + fragment.length()) != 0) + { + return false; + } + + starting_pos += fragment.length(); + + // Should only happen when we have a static fragment after a param + // Example: '/users/:id/subscriptions' + // The 'subscriptions' fragment here does not have a corresponding param + if (i >= param_names_.size()) { continue; } + + auto sep_pos = request.path.find(separator, starting_pos); + if (sep_pos == std::string::npos) { sep_pos = request.path.length(); } + + const auto& param_name = param_names_[i]; + + request.path_params.emplace( + param_name, request.path.substr(starting_pos, sep_pos - starting_pos)); + + // Mark everything up to '/' as matched + starting_pos = sep_pos + 1; + } + // Returns false if the path is longer than the pattern + return starting_pos >= request.path.length(); + } + + inline bool RegexMatcher::match(Request& request) const + { + request.path_params.clear(); + return std::regex_match(request.path, request.matches, regex_); + } + } // namespace detail + + // HTTP server implementation + inline Server::Server() : + new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) + { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + } + + inline Server::~Server() = default; + + inline std::unique_ptr + Server::make_matcher(const std::string& pattern) + { + if (pattern.find("/:") != std::string::npos) + { + return detail::make_unique(pattern); + } + else + { + return detail::make_unique(pattern); + } + } + + inline Server& Server::Get(const std::string& pattern, Handler handler) + { + get_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; + } + + inline Server& Server::Post(const std::string& pattern, Handler handler) + { + std::cout << "post:" << pattern << std::endl; + post_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; + } + + inline Server& Server::Post(const std::string& pattern, + HandlerWithContentReader handler) + { + post_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; + } + + inline Server& Server::Put(const std::string& pattern, Handler handler) + { + put_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; + } + + inline Server& Server::Put(const std::string& pattern, + HandlerWithContentReader handler) + { + put_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; + } + + inline Server& Server::Patch(const std::string& pattern, Handler handler) + { + patch_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; + } + + inline Server& Server::Patch(const std::string& pattern, + HandlerWithContentReader handler) + { + patch_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; + } + + inline Server& Server::Delete(const std::string& pattern, Handler handler) + { + delete_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; + } + + inline Server& Server::Delete(const std::string& pattern, + HandlerWithContentReader handler) + { + delete_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; + } + + inline Server& Server::Options(const std::string& pattern, Handler handler) + { + options_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; + } + + inline bool Server::set_base_dir(const std::string& dir, + const std::string& mount_point) + { + return set_mount_point(mount_point, dir); + } + + inline bool Server::set_mount_point(const std::string& mount_point, + const std::string& dir, Headers headers) + { + detail::FileStat stat(dir); + if (stat.is_dir()) + { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') + { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; + } + + inline bool Server::remove_mount_point(const std::string& mount_point) + { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) + { + if (it->mount_point == mount_point) + { + base_dirs_.erase(it); + return true; + } + } + return false; + } + + inline Server& + Server::set_file_extension_and_mimetype_mapping(const std::string& ext, + const std::string& mime) + { + file_extension_and_mimetype_map_[ext] = mime; + return *this; + } + + inline Server& Server::set_default_file_mimetype(const std::string& mime) + { + default_file_mimetype_ = mime; + return *this; + } + + inline Server& Server::set_file_request_handler(Handler handler) + { + file_request_handler_ = std::move(handler); + return *this; + } + + inline Server& Server::set_error_handler_core(HandlerWithResponse handler, + std::true_type) + { + error_handler_ = std::move(handler); + return *this; + } + + inline Server& Server::set_error_handler_core(Handler handler, + std::false_type) + { + error_handler_ = [handler](const Request& req, Response& res) + { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; + } + + inline Server& Server::set_exception_handler(ExceptionHandler handler) + { + exception_handler_ = std::move(handler); + return *this; + } + + inline Server& Server::set_pre_routing_handler(HandlerWithResponse handler) + { + pre_routing_handler_ = std::move(handler); + return *this; + } + + inline Server& Server::set_post_routing_handler(Handler handler) + { + post_routing_handler_ = std::move(handler); + return *this; + } + + inline Server& Server::set_logger(Logger logger) + { + logger_ = std::move(logger); + return *this; + } + + inline Server& + Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) + { + expect_100_continue_handler_ = std::move(handler); + return *this; + } + + inline Server& Server::set_address_family(int family) + { + address_family_ = family; + return *this; + } + + inline Server& Server::set_tcp_nodelay(bool on) + { + tcp_nodelay_ = on; + return *this; + } + + inline Server& Server::set_ipv6_v6only(bool on) + { + ipv6_v6only_ = on; + return *this; + } + + inline Server& Server::set_socket_options(SocketOptions socket_options) + { + socket_options_ = std::move(socket_options); + return *this; + } + + inline Server& Server::set_default_headers(Headers headers) + { + default_headers_ = std::move(headers); + return *this; + } + + inline Server& Server::set_header_writer( + std::function const& writer) + { + header_writer_ = writer; + return *this; + } + + inline Server& Server::set_keep_alive_max_count(size_t count) + { + keep_alive_max_count_ = count; + return *this; + } + + inline Server& Server::set_keep_alive_timeout(time_t sec) + { + keep_alive_timeout_sec_ = sec; + return *this; + } + + inline Server& Server::set_read_timeout(time_t sec, time_t usec) + { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; + } + + inline Server& Server::set_write_timeout(time_t sec, time_t usec) + { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; + } + + inline Server& Server::set_idle_interval(time_t sec, time_t usec) + { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; + } + + inline Server& Server::set_payload_max_length(size_t length) + { + payload_max_length_ = length; + return *this; + } + + inline bool Server::bind_to_port(const std::string& host, int port, + int socket_flags) + { + auto ret = bind_internal(host, port, socket_flags); + if (ret == -1) { is_decommisioned = true; } + return ret >= 0; + } + + inline int Server::bind_to_any_port(const std::string& host, int socket_flags) + { + auto ret = bind_internal(host, 0, socket_flags); + if (ret == -1) { is_decommisioned = true; } + return ret; + } + + inline bool Server::listen_after_bind() { return listen_internal(); } + + inline bool Server::listen(const std::string& host, int port, + int socket_flags) + { + bool b1 = bind_to_port(host, port, socket_flags); + if (!b1) + { + std::cout << "端口绑定失败!" << host << ":" << port << std::endl; + } + return b1 && listen_internal(); + } + + inline bool Server::is_running() const { return is_running_; } + + inline void Server::wait_until_ready() const + { + while (!is_running_ && !is_decommisioned) + { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } + } + + inline void Server::stop() + { + if (is_running_) + { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + is_decommisioned = false; + } + + inline void Server::decommission() { is_decommisioned = true; } + + inline bool Server::parse_request_line(const char* s, Request& req) const + { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char* b, const char* e) + { + switch (count) + { + case 0: + req.method = std::string(b, e); + break; + case 1: + req.target = std::string(b, e); + break; + case 2: + req.version = std::string(b, e); + break; + default: + break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) + { + if (req.target[i] == '#') + { + req.target.erase(i); + break; + } + } + + detail::divide(req.target, '?', + [&](const char* lhs_data, std::size_t lhs_size, + const char* rhs_data, std::size_t rhs_size) + { + req.path = detail::decode_url( + std::string(lhs_data, lhs_size), false); + detail::parse_query_text(rhs_data, rhs_size, req.params); + }); + } + + return true; + } + + inline bool Server::write_response(Stream& strm, bool close_connection, + Request& req, Response& res) + { + // NOTE: `req.ranges` should be empty, otherwise it will be applied + // incorrectly to the error content. + req.ranges.clear(); + return write_response_core(strm, close_connection, req, res, false); + } + + inline bool Server::write_response_with_content(Stream& strm, + bool close_connection, + const Request& req, + Response& res) + { + return write_response_core(strm, close_connection, req, res, true); + } + + inline bool Server::write_response_core(Stream& strm, bool close_connection, + const Request& req, Response& res, + bool need_apply_ranges) + { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) + { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") + { + res.set_header("Connection", "close"); + } + else + { + std::string s = "timeout="; + s += std::to_string(keep_alive_timeout_sec_); + s += ", max="; + s += std::to_string(keep_alive_max_count_); + res.set_header("Keep-Alive", s); + } + + if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) && + !res.has_header("Content-Type")) + { + res.set_header("Content-Type", "text/plain"); + } + + if (res.body.empty() && !res.content_length_ && !res.content_provider_ && + !res.has_header("Content-Length")) + { + res.set_header("Content-Length", "0"); + } + + if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) + { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + if (!detail::write_response_line(bstrm, res.status)) { return false; } + if (!header_writer_(bstrm, res.headers)) { return false; } + + // Flush buffer + auto& data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") + { + if (!res.body.empty()) + { + if (!detail::write_data(strm, res.body.data(), res.body.size())) + { + ret = false; + } + } + else if (res.content_provider_) + { + if (write_content_with_provider(strm, req, res, boundary, content_type)) + { + res.content_provider_success_ = true; + } + else + { + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; + } + + inline bool + Server::write_content_with_provider(Stream& strm, const Request& req, + Response& res, const std::string& boundary, + const std::string& content_type) + { + auto is_shutting_down = [this]() + { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) + { + if (req.ranges.empty()) + { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } + else if (req.ranges.size() == 1) + { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + return detail::write_content(strm, res.content_provider_, + offset_and_length.first, + offset_and_length.second, is_shutting_down); + } + else + { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, res.content_length_, + is_shutting_down); + } + } + else + { + if (res.is_chunked_content_provider_) + { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) + { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } + else if (type == detail::EncodingType::Brotli) + { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } + else + { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } + else + { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } + } + + inline bool Server::read_content(Stream& strm, Request& req, Response& res) + { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char* buf, size_t n) + { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData& file) + { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) + { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char* buf, size_t n) + { + auto& content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) + { + const auto& content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) + { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) + { + res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; + } + + inline bool Server::read_content_with_content_receiver( + Stream& strm, Request& req, Response& res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) + { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); + } + + inline bool + Server::read_content_core(Stream& strm, Request& req, Response& res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) const + { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) + { + const auto& content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) + { + res.status = StatusCode::BadRequest_400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char* buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) + { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } + else + { + out = [receiver](const char* buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) + { + return receiver(buf, n); + }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) + { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) + { + return false; + } + + if (req.is_multipart_form_data()) + { + if (!multipart_form_data_parser.is_valid()) + { + res.status = StatusCode::BadRequest_400; + return false; + } + } + + return true; + } + + inline bool Server::handle_file_request(const Request& req, Response& res, + bool head) + { + for (const auto& entry : base_dirs_) + { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) + { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) + { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + detail::FileStat stat(path); + + if (stat.is_dir()) + { + res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301); + return true; + } + + if (stat.is_file()) + { + for (const auto& kv : entry.headers) + { + res.set_header(kv.first, kv.second); + } + + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { return false; } + + res.set_content_provider( + mm->size(), + detail::find_content_type(path, file_extension_and_mimetype_map_, + default_file_mimetype_), + [mm](size_t offset, size_t length, DataSink& sink) -> bool + { + sink.write(mm->data() + offset, length); + return true; + }); + + if (!head && file_request_handler_) + { + file_request_handler_(req, res); + } + + return true; + } + } + } + } + return false; + } + + inline socket_t + Server::create_server_socket(const std::string& host, int port, + int socket_flags, + SocketOptions socket_options) const + { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + ipv6_v6only_, std::move(socket_options), + [](socket_t sock, struct addrinfo& ai, bool& /*quit*/) -> bool + { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) + { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); + } + + inline int Server::bind_internal(const std::string& host, int port, + int socket_flags) + { + if (is_decommisioned) { return -1; } + + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) + { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) + { + return -1; + } + if (addr.ss_family == AF_INET) + { + return ntohs(reinterpret_cast(&addr)->sin_port); + } + else if (addr.ss_family == AF_INET6) + { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } + else + { + return -1; + } + } + else + { + return port; + } + } + + inline bool Server::listen_internal() + { + if (is_decommisioned) { return false; } + + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) + { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) + { + // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + +#if defined _WIN32 + // sockets conneced via WASAccept inherit flags NO_HANDLE_INHERIT, + // OVERLAPPED + socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0); +#elif defined SOCK_CLOEXEC + socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC); +#else + socket_t sock = accept(svr_sock_, nullptr, nullptr); +#endif + + if (sock == INVALID_SOCKET) + { + if (errno == EMFILE) + { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::microseconds{1}); + continue; + } + else if (errno == EINTR || errno == EAGAIN) + { + continue; + } + if (svr_sock_ != INVALID_SOCKET) + { + detail::close_socket(svr_sock_); + ret = false; + } + else + { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + if (!task_queue->enqueue( + [this, sock]() { process_and_close_socket(sock); })) + { + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + } + + task_queue->shutdown(); + } + + is_decommisioned = !ret; + return ret; + } + + inline bool Server::routing(Request& req, Response& res, Stream& strm) + { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) + { + return true; + } + + // File handler + auto is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) + { + return true; + } + + if (detail::expect_content(req)) + { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) + { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) + { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") + { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) + { + return true; + } + } + else if (req.method == "PUT") + { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) + { + return true; + } + } + else if (req.method == "PATCH") + { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) + { + return true; + } + } + else if (req.method == "DELETE") + { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) + { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") + { + return dispatch_request(req, res, get_handlers_); + } + else if (req.method == "POST") + { + return dispatch_request(req, res, post_handlers_); + } + else if (req.method == "PUT") + { + return dispatch_request(req, res, put_handlers_); + } + else if (req.method == "DELETE") + { + return dispatch_request(req, res, delete_handlers_); + } + else if (req.method == "OPTIONS") + { + return dispatch_request(req, res, options_handlers_); + } + else if (req.method == "PATCH") + { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = StatusCode::BadRequest_400; + return false; + } + + inline bool Server::dispatch_request(Request& req, Response& res, + const Handlers& handlers) const + { + for (const auto& x : handlers) + { + const auto& matcher = x.first; + const auto& handler = x.second; + + if (matcher->match(req)) + { + handler(req, res); + return true; + } + } + return false; + } + + inline void Server::apply_ranges(const Request& req, Response& res, + std::string& content_type, + std::string& boundary) const + { + if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) + { + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) + { + content_type = it->second; + res.headers.erase(it); + } + + boundary = detail::make_multipart_data_boundary(); + + res.set_header("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) + { + if (res.content_length_ > 0) + { + size_t length = 0; + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) + { + length = res.content_length_; + } + else if (req.ranges.size() == 1) + { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.content_length_); + res.set_header("Content-Range", content_range); + } + else + { + length = detail::get_multipart_ranges_data_length( + req, boundary, content_type, res.content_length_); + } + res.set_header("Content-Length", std::to_string(length)); + } + else + { + if (res.content_provider_) + { + if (res.is_chunked_content_provider_) + { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) + { + res.set_header("Content-Encoding", "gzip"); + } + else if (type == detail::EncodingType::Brotli) + { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } + else + { + if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) + { + ; + } + else if (req.ranges.size() == 1) + { + auto offset_and_length = + detail::get_range_offset_and_length(req.ranges[0], res.body.size()); + auto offset = offset_and_length.first; + auto length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.body.size()); + res.set_header("Content-Range", content_range); + + assert(offset + length <= res.body.size()); + res.body = res.body.substr(offset, length); + } + else + { + std::string data; + detail::make_multipart_ranges_data(req, res, boundary, content_type, + res.body.size(), data); + res.body.swap(data); + } + + if (type != detail::EncodingType::None) + { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) + { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } + else if (type == detail::EncodingType::Brotli) + { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) + { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char* data, size_t data_len) + { + compressed.append(data, data_len); + return true; + })) + { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } + } + + inline bool Server::dispatch_request_for_content_reader( + Request& req, Response& res, ContentReader content_reader, + const HandlersForContentReader& handlers) const + { + for (const auto& x : handlers) + { + const auto& matcher = x.first; + const auto& handler = x.second; + + if (matcher->match(req)) + { + handler(req, res, content_reader); + return true; + } + } + return false; + } + + inline bool + Server::process_request(Stream& strm, const std::string& remote_addr, + int remote_port, const std::string& local_addr, + int local_port, bool close_connection, + bool& connection_closed, + const std::function& setup_request) + { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + + Response res; + res.version = "HTTP/1.1"; + res.headers = default_headers_; + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::InternalServerError_500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) + { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::UriTooLong_414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) + { + res.status = StatusCode::BadRequest_400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") + { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") + { + connection_closed = true; + } + + req.remote_addr = remote_addr; + req.remote_port = remote_port; + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + req.local_addr = local_addr; + req.local_port = local_port; + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) + { + const auto& range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) + { + res.status = StatusCode::RangeNotSatisfiable_416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") + { + int status = StatusCode::Continue_100; + if (expect_100_continue_handler_) + { + status = expect_100_continue_handler_(req, res); + } + switch (status) + { + case StatusCode::Continue_100: + case StatusCode::ExpectationFailed_417: + detail::write_response_line(strm, status); + strm.write("\r\n"); + break; + default: + connection_closed = true; + return write_response(strm, true, req, res); + } + } + + // Routing + auto routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try + { + routed = routing(req, res, strm); + } + catch (std::exception& e) + { + if (exception_handler_) + { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } + else + { + res.status = StatusCode::InternalServerError_500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) + { + switch (s[i]) + { + case '\r': + val += "\\r"; + break; + case '\n': + val += "\\n"; + break; + default: + val += s[i]; + break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) + { + if (exception_handler_) + { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } + else + { + res.status = StatusCode::InternalServerError_500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + if (routed) + { + if (res.status == -1) + { + res.status = req.ranges.empty() + ? StatusCode::OK_200 + : StatusCode::PartialContent_206; + } + + if (detail::range_error(req, res)) + { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::RangeNotSatisfiable_416; + return write_response(strm, close_connection, req, res); + } + + // Serve file content by using a content provider + if (!res.file_content_path_.empty()) + { + const auto& path = res.file_content_path_; + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) + { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::NotFound_404; + return write_response(strm, close_connection, req, res); + } + + auto content_type = res.file_content_content_type_; + if (content_type.empty()) + { + content_type = detail::find_content_type( + path, file_extension_and_mimetype_map_, default_file_mimetype_); + } + + res.set_content_provider( + mm->size(), content_type, + [mm](size_t offset, size_t length, DataSink& sink) -> bool + { + sink.write(mm->data() + offset, length); + return true; + }); + } + + return write_response_with_content(strm, close_connection, req, res); + } + else + { + if (res.status == -1) { res.status = StatusCode::NotFound_404; } + + return write_response(strm, close_connection, req, res); + } + } + + inline bool Server::is_valid() const { return true; } + + inline bool Server::process_and_close_socket(socket_t sock) + { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [&](Stream& strm, bool close_connection, bool& connection_closed) + { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; + } + + // HTTP client implementation + inline ClientImpl::ClientImpl(const std::string& host) : + ClientImpl(host, 80, std::string(), std::string()) + { + } + + inline ClientImpl::ClientImpl(const std::string& host, int port) : + ClientImpl(host, port, std::string(), std::string()) + { + } + + inline ClientImpl::ClientImpl(const std::string& host, int port, + const std::string& client_cert_path, + const std::string& client_key_path) : + host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port), + host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) + { + } + + inline ClientImpl::~ClientImpl() + { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); + } + + inline bool ClientImpl::is_valid() const { return true; } + + inline void ClientImpl::copy_settings(const ClientImpl& rhs) + { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + ipv6_v6only_ = rhs.ipv6_v6only_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; + server_hostname_verification_ = rhs.server_hostname_verification_; + server_certificate_verifier_ = rhs.server_certificate_verifier_; +#endif + logger_ = rhs.logger_; + } + + inline socket_t ClientImpl::create_client_socket(Error& error) const + { + if (!proxy_host_.empty() && proxy_port_ != -1) + { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + ipv6_v6only_, socket_options_, connection_timeout_sec_, + connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) { ip = it->second; } + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); + } + + inline bool ClientImpl::create_and_connect_socket(Socket& socket, + Error& error) + { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; + } + + inline void ClientImpl::shutdown_ssl(Socket& /*socket*/, + bool /*shutdown_gracefully*/) + { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + + inline void ClientImpl::shutdown_socket(Socket& socket) const + { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); + } + + inline void ClientImpl::close_socket(Socket& socket) + { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; + } + + inline bool ClientImpl::read_response_line(Stream& strm, const Request& req, + Response& res) const + { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) + { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == StatusCode::Continue_100) + { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; + } + + inline bool ClientImpl::send(Request& req, Response& res, Error& error) + { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) + { + assert(!ret); + ret = send_(req, res, error); + } + return ret; + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline bool ClientImpl::is_ssl_peer_could_be_closed(SSL *ssl) const { + char buf[1]; + return !SSL_peek(ssl, buf, 1) && + SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN; +} +#endif + + inline bool ClientImpl::send_(Request& req, Response& res, Error& error) + { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) + { + is_alive = detail::is_socket_alive(socket_.sock); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_alive && is_ssl()) { + if (is_ssl_peer_could_be_closed(socket_.ssl)) { is_alive = false; } + } +#endif + + if (!is_alive) + { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) + { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) + { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto& header : default_headers_) + { + if (req.headers.find(header.first) == req.headers.end()) + { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() + { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) + { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) + { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, [&](Stream& strm) + { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) + { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; + } + + inline Result ClientImpl::send(const Request& req) + { + auto req2 = req; + return send_(std::move(req2)); + } + + inline Result ClientImpl::send_(Request&& req) + { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; + } + + inline bool ClientImpl::handle_request(Stream& strm, Request& req, + Response& res, bool close_connection, + Error& error) + { + if (req.path.empty()) + { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) + { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } + else + { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) + { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. + + // This is safe to call because handle_request is only called by send_ + // which locks the request mutex during the process. It would be a bug + // to call it from a different thread since it's a thread-safety issue + // to do these things to the socket if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + if (300 < res.status && res.status < 400 && follow_location_) + { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == StatusCode::Unauthorized_401 || + res.status == StatusCode::ProxyAuthenticationRequired_407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; + } + + inline bool ClientImpl::redirect(Request& req, Response& res, Error& error) + { + if (req.redirect_count_ == 0) + { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) + { + next_port = std::stoi(port_str); + } + else if (!next_scheme.empty()) + { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) + { + return detail::redirect(*this, req, res, path, location, error); + } + else + { + if (next_scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host, next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } + else + { + ClientImpl cli(next_host, next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } + } + + inline bool ClientImpl::write_content_with_provider(Stream& strm, + const Request& req, + Error& error) const + { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) + { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } + else + { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } + } + + inline bool ClientImpl::write_request(Stream& strm, Request& req, + bool close_connection, Error& error) + { + // Prepare additional headers + if (close_connection) + { + if (!req.has_header("Connection")) + { + req.set_header("Connection", "close"); + } + } + + if (!req.has_header("Host")) + { + if (is_ssl()) + { + if (port_ == 443) + { + req.set_header("Host", host_); + } + else + { + req.set_header("Host", host_and_port_); + } + } + else + { + if (port_ == 80) + { + req.set_header("Host", host_); + } + else + { + req.set_header("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); } + + if (!req.content_receiver) + { + if (!req.has_header("Accept-Encoding")) + { + std::string accept_encoding; +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + accept_encoding = "br"; +#endif +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (!accept_encoding.empty()) { accept_encoding += ", "; } + accept_encoding += "gzip, deflate"; +#endif + req.set_header("Accept-Encoding", accept_encoding); + } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) + { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.set_header("User-Agent", agent); + } +#endif + }; + + if (req.body.empty()) + { + if (req.content_provider_) + { + if (!req.is_chunked_content_provider_) + { + if (!req.has_header("Content-Length")) + { + auto length = std::to_string(req.content_length_); + req.set_header("Content-Length", length); + } + } + } + else + { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") + { + req.set_header("Content-Length", "0"); + } + } + } + else + { + if (!req.has_header("Content-Type")) + { + req.set_header("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) + { + auto length = std::to_string(req.body.size()); + req.set_header("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) + { + if (!req.has_header("Authorization")) + { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) + { + if (!req.has_header("Proxy-Authorization")) + { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) + { + if (!req.has_header("Authorization")) + { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) + { + if (!req.has_header("Proxy-Authorization")) + { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto& path_with_query = + req.params.empty() + ? req.path + : append_query_params(req.path, req.params); + + const auto& path = + url_encode_ ? detail::encode_url(path_with_query) : path_with_query; + + detail::write_request_line(bstrm, req.method, path); + + header_writer_(bstrm, req.headers); + + // Flush buffer + auto& data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) + { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) + { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) + { + error = Error::Write; + return false; + } + + return true; + } + + inline std::unique_ptr ClientImpl::send_with_content_provider( + Request& req, const char* body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string& content_type, Error& error) + { + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.set_header("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) + { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } + else if (content_provider_without_length) + { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.set_header("Transfer-Encoding", "chunked"); + } + else + { + req.body.assign(body, content_length); + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; + } + + inline Result ClientImpl::send_with_content_provider( + const std::string& method, const std::string& path, const Headers& headers, + const char* body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string& content_type, Progress progress) + { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + req.progress = progress; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; + } + + inline std::string + ClientImpl::adjust_host_string(const std::string& host) const + { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; + } + + inline bool ClientImpl::process_request(Stream& strm, Request& req, + Response& res, bool close_connection, + Error& error) + { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + if (is_ssl_peer_could_be_closed(socket_.ssl)) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) + { + error = Error::Read; + return false; + } + + // Body + if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" && + req.method != "CONNECT") + { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) + { + if (!req.response_handler(res)) + { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char* buf, size_t n, uint64_t off, uint64_t len) + { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char* buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) + { + assert(res.body.size() + n <= res.body.max_size()); + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) + { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + if (res.has_header("Content-Length")) + { + if (!req.content_receiver) + { + auto len = res.get_header_value_u64("Content-Length"); + if (len > res.body.max_size()) + { + error = Error::Read; + return false; + } + res.body.reserve(len); + } + } + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) + { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + // Log + if (logger_) { logger_(req, res); } + + return true; + } + + inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string& boundary, const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items) const + { + size_t cur_item = 0; + size_t cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink& sink) mutable -> bool + { + if (!offset && !items.empty()) + { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } + else if (cur_item < provider_items.size()) + { + if (!cur_start) + { + const auto& begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + auto has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + { + return false; + } + + if (!has_data) + { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } + else + { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; + } + + inline bool + ClientImpl::process_socket(const Socket& socket, + std::function callback) + { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); + } + + inline bool ClientImpl::is_ssl() const { return false; } + + inline Result ClientImpl::Get(const std::string& path) + { + return Get(path, Headers(), Progress()); + } + + inline Result ClientImpl::Get(const std::string& path, Progress progress) + { + return Get(path, Headers(), std::move(progress)); + } + + inline Result ClientImpl::Get(const std::string& path, const Headers& headers) + { + return Get(path, headers, Progress()); + } + + inline Result ClientImpl::Get(const std::string& path, const Headers& headers, + Progress progress) + { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); + } + + inline Result ClientImpl::Get(const std::string& path, + ContentReceiver content_receiver) + { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); + } + + inline Result ClientImpl::Get(const std::string& path, + ContentReceiver content_receiver, + Progress progress) + { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); + } + + inline Result ClientImpl::Get(const std::string& path, const Headers& headers, + ContentReceiver content_receiver) + { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); + } + + inline Result ClientImpl::Get(const std::string& path, const Headers& headers, + ContentReceiver content_receiver, + Progress progress) + { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); + } + + inline Result ClientImpl::Get(const std::string& path, + ResponseHandler response_handler, + ContentReceiver content_receiver) + { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); + } + + inline Result ClientImpl::Get(const std::string& path, const Headers& headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) + { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); + } + + inline Result ClientImpl::Get(const std::string& path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) + { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + inline Result ClientImpl::Get(const std::string& path, const Headers& headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) + { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char* data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) + { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); + } + + inline Result ClientImpl::Get(const std::string& path, const Params& params, + const Headers& headers, Progress progress) + { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(progress)); + } + + inline Result ClientImpl::Get(const std::string& path, const Params& params, + const Headers& headers, + ContentReceiver content_receiver, + Progress progress) + { + return Get(path, params, headers, nullptr, std::move(content_receiver), + std::move(progress)); + } + + inline Result ClientImpl::Get(const std::string& path, const Params& params, + const Headers& headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) + { + if (params.empty()) + { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + inline Result ClientImpl::Head(const std::string& path) + { + return Head(path, Headers()); + } + + inline Result ClientImpl::Head(const std::string& path, + const Headers& headers) + { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); + } + + inline Result ClientImpl::Post(const std::string& path) + { + return Post(path, std::string(), std::string()); + } + + inline Result ClientImpl::Post(const std::string& path, + const Headers& headers) + { + return Post(path, headers, nullptr, 0, std::string()); + } + + inline Result ClientImpl::Post(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type) + { + return Post(path, Headers(), body, content_length, content_type, nullptr); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type) + { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type, nullptr); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, + Progress progress) + { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); + } + + inline Result ClientImpl::Post(const std::string& path, const std::string& body, + const std::string& content_type) + { + return Post(path, Headers(), body, content_type); + } + + inline Result ClientImpl::Post(const std::string& path, const std::string& body, + const std::string& content_type, + Progress progress) + { + return Post(path, Headers(), body, content_type, progress); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type) + { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + nullptr); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type, + Progress progress) + { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); + } + + inline Result ClientImpl::Post(const std::string& path, const Params& params) + { + return Post(path, Headers(), params); + } + + inline Result ClientImpl::Post(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); + } + + inline Result ClientImpl::Post(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return Post(path, Headers(), std::move(content_provider), content_type); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, nullptr); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + nullptr); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + const Params& params) + { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + const Params& params, Progress progress) + { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded", + progress); + } + + inline Result ClientImpl::Post(const std::string& path, + const MultipartFormDataItems& items) + { + return Post(path, Headers(), items); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items) + { + const auto& boundary = detail::make_multipart_data_boundary(); + const auto& content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto& body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); + } + + inline Result ClientImpl::Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const std::string& boundary) + { + if (!detail::is_multipart_boundary_chars_valid(boundary)) + { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto& content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto& body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); + } + + inline Result + ClientImpl::Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items) + { + const auto& boundary = detail::make_multipart_data_boundary(); + const auto& content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, nullptr); + } + + inline Result ClientImpl::Put(const std::string& path) + { + return Put(path, std::string(), std::string()); + } + + inline Result ClientImpl::Put(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type) + { + return Put(path, Headers(), body, content_length, content_type); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type) + { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type, nullptr); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, + Progress progress) + { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); + } + + inline Result ClientImpl::Put(const std::string& path, const std::string& body, + const std::string& content_type) + { + return Put(path, Headers(), body, content_type); + } + + inline Result ClientImpl::Put(const std::string& path, const std::string& body, + const std::string& content_type, + Progress progress) + { + return Put(path, Headers(), body, content_type, progress); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type) + { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + nullptr); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type, + Progress progress) + { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); + } + + inline Result ClientImpl::Put(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); + } + + inline Result ClientImpl::Put(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return Put(path, Headers(), std::move(content_provider), content_type); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, nullptr); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + nullptr); + } + + inline Result ClientImpl::Put(const std::string& path, const Params& params) + { + return Put(path, Headers(), params); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + const Params& params) + { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + const Params& params, Progress progress) + { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded", + progress); + } + + inline Result ClientImpl::Put(const std::string& path, + const MultipartFormDataItems& items) + { + return Put(path, Headers(), items); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items) + { + const auto& boundary = detail::make_multipart_data_boundary(); + const auto& content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto& body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); + } + + inline Result ClientImpl::Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const std::string& boundary) + { + if (!detail::is_multipart_boundary_chars_valid(boundary)) + { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto& content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto& body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); + } + + inline Result + ClientImpl::Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items) + { + const auto& boundary = detail::make_multipart_data_boundary(); + const auto& content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, nullptr); + } + + inline Result ClientImpl::Patch(const std::string& path) + { + return Patch(path, std::string(), std::string()); + } + + inline Result ClientImpl::Patch(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type) + { + return Patch(path, Headers(), body, content_length, content_type); + } + + inline Result ClientImpl::Patch(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type, + Progress progress) + { + return Patch(path, Headers(), body, content_length, content_type, progress); + } + + inline Result ClientImpl::Patch(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type) + { + return Patch(path, headers, body, content_length, content_type, nullptr); + } + + inline Result ClientImpl::Patch(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, + Progress progress) + { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type, progress); + } + + inline Result ClientImpl::Patch(const std::string& path, + const std::string& body, + const std::string& content_type) + { + return Patch(path, Headers(), body, content_type); + } + + inline Result ClientImpl::Patch(const std::string& path, + const std::string& body, + const std::string& content_type, + Progress progress) + { + return Patch(path, Headers(), body, content_type, progress); + } + + inline Result ClientImpl::Patch(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type) + { + return Patch(path, headers, body, content_type, nullptr); + } + + inline Result ClientImpl::Patch(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type, + Progress progress) + { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); + } + + inline Result ClientImpl::Patch(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); + } + + inline Result ClientImpl::Patch(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return Patch(path, Headers(), std::move(content_provider), content_type); + } + + inline Result ClientImpl::Patch(const std::string& path, const Headers& headers, + size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, nullptr); + } + + inline Result ClientImpl::Patch(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + nullptr); + } + + inline Result ClientImpl::Delete(const std::string& path) + { + return Delete(path, Headers(), std::string(), std::string()); + } + + inline Result ClientImpl::Delete(const std::string& path, + const Headers& headers) + { + return Delete(path, headers, std::string(), std::string()); + } + + inline Result ClientImpl::Delete(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type) + { + return Delete(path, Headers(), body, content_length, content_type); + } + + inline Result ClientImpl::Delete(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type, + Progress progress) + { + return Delete(path, Headers(), body, content_length, content_type, progress); + } + + inline Result ClientImpl::Delete(const std::string& path, + const Headers& headers, const char* body, + size_t content_length, + const std::string& content_type) + { + return Delete(path, headers, body, content_length, content_type, nullptr); + } + + inline Result ClientImpl::Delete(const std::string& path, + const Headers& headers, const char* body, + size_t content_length, + const std::string& content_type, + Progress progress) + { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + req.progress = progress; + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + req.body.assign(body, content_length); + + return send_(std::move(req)); + } + + inline Result ClientImpl::Delete(const std::string& path, + const std::string& body, + const std::string& content_type) + { + return Delete(path, Headers(), body.data(), body.size(), content_type); + } + + inline Result ClientImpl::Delete(const std::string& path, + const std::string& body, + const std::string& content_type, + Progress progress) + { + return Delete(path, Headers(), body.data(), body.size(), content_type, + progress); + } + + inline Result ClientImpl::Delete(const std::string& path, + const Headers& headers, + const std::string& body, + const std::string& content_type) + { + return Delete(path, headers, body.data(), body.size(), content_type); + } + + inline Result ClientImpl::Delete(const std::string& path, + const Headers& headers, + const std::string& body, + const std::string& content_type, + Progress progress) + { + return Delete(path, headers, body.data(), body.size(), content_type, + progress); + } + + inline Result ClientImpl::Options(const std::string& path) + { + return Options(path, Headers()); + } + + inline Result ClientImpl::Options(const std::string& path, + const Headers& headers) + { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); + } + + inline void ClientImpl::stop() + { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) + { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + inline std::string ClientImpl::host() const { return host_; } + + inline int ClientImpl::port() const { return port_; } + + inline size_t ClientImpl::is_socket_open() const + { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); + } + + inline socket_t ClientImpl::socket() const { return socket_.sock; } + + inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) + { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; + } + + inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) + { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + } + + inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) + { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + } + + inline void ClientImpl::set_basic_auth(const std::string& username, + const std::string& password) + { + basic_auth_username_ = username; + basic_auth_password_ = password; + } + + inline void ClientImpl::set_bearer_token_auth(const std::string& token) + { + bearer_token_auth_token_ = token; + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + + inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + + inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + + inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + + inline void + ClientImpl::set_hostname_addr_map(std::map addr_map) + { + addr_map_ = std::move(addr_map); + } + + inline void ClientImpl::set_default_headers(Headers headers) + { + default_headers_ = std::move(headers); + } + + inline void ClientImpl::set_header_writer( + std::function const& writer) + { + header_writer_ = writer; + } + + inline void ClientImpl::set_address_family(int family) + { + address_family_ = family; + } + + inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + + inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; } + + inline void ClientImpl::set_socket_options(SocketOptions socket_options) + { + socket_options_ = std::move(socket_options); + } + + inline void ClientImpl::set_compress(bool on) { compress_ = on; } + + inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + + inline void ClientImpl::set_interface(const std::string& intf) + { + interface_ = intf; + } + + inline void ClientImpl::set_proxy(const std::string& host, int port) + { + proxy_host_ = host; + proxy_port_ = port; + } + + inline void ClientImpl::set_proxy_basic_auth(const std::string& username, + const std::string& password) + { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; + } + + inline void ClientImpl::set_proxy_bearer_token_auth(const std::string& token) + { + proxy_bearer_token_auth_token_ = token; + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} + +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} + +inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert, + std::size_t size) const { + auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); + auto se = detail::scope_exit([&] { BIO_free_all(mem); }); + if (!mem) { return nullptr; } + + auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); + if (!inf) { return nullptr; } + + auto cts = X509_STORE_new(); + if (cts) { + for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { + auto itmp = sk_X509_INFO_value(inf, i); + if (!itmp) { continue; } + + if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); } + if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); } + } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + return cts; +} + +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} + +inline void ClientImpl::enable_server_hostname_verification(bool enabled) { + server_hostname_verification_ = enabled; +} + +inline void ClientImpl::set_server_certificate_verifier( + std::function verifier) { + server_certificate_verifier_ = verifier; +} +#endif + + inline void ClientImpl::set_logger(Logger logger) + { + logger_ = std::move(logger); + } + + /* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { +#ifdef _WIN32 + SSL_shutdown(ssl); +#else + timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); + + auto ret = SSL_shutdown(ssl); + while (ret == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + ret = SSL_shutdown(ssl); + } +#endif + } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + auto res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() = default; + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::microseconds{10}); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, + reinterpret_cast(const_cast(private_key_password))); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1 || + SSL_CTX_check_private_key(ctx_) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + + std::lock_guard guard(ctx_mutex_); + + SSL_CTX_use_certificate(ctx_, cert); + SSL_CTX_use_PrivateKey(ctx_, private_key); + + if (client_ca_cert_store != nullptr) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + } +} + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [&](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, + connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); + } + + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key, + const std::string &private_key_password) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); + } + + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline void SSLClient::load_ca_cert_store(const char *ca_cert, + std::size_t size) { + set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size)); +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response proxy_res; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(proxy_res, auth, true)) { + proxy_res = Response(); + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } + } + + // If status code is not 200, proxy request is failed. + // Set error to ProxyConnection and return proxy response + // as the response of the request + if (proxy_res.status != StatusCode::OK_200) { + error = Error::ProxyConnection; + res = std::move(proxy_res); + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + return false; + } + + return true; +} + +inline bool SSLClient::load_certs() { + auto ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + if (server_certificate_verifier_) { + if (!server_certificate_verifier_(ssl2)) { + error = Error::SSLServerVerification; + return false; + } + } else { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + auto se = detail::scope_exit([&] { X509_free(server_cert); }); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (server_hostname_verification_) { + if (!verify_host(server_cert)) { + error = Error::SSLServerHostnameVerification; + return false; + } + } + } + } + + return true; + }, + [&](SSL *ssl2) { +#if defined(OPENSSL_IS_BORINGSSL) + SSL_set_tlsext_host_name(ssl2, host_.c_str()); +#else + // NOTE: Direct call instead of using the OpenSSL macro to suppress + // -Wold-style-cast warning + SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, + static_cast(const_cast(host_.c_str()))); +#endif + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock, + shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6{}; + struct in_addr addr{}; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = + reinterpret_cast(ASN1_STRING_get0_data(val->d.ia5)); + auto name_len = static_cast(ASN1_STRING_length(val->d.ia5)); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free(const_cast( + reinterpret_cast(alt_names))); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(b, e); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + + // Universal client implementation + inline Client::Client(const std::string& scheme_host_port) : + Client(scheme_host_port, std::string(), std::string()) + { + } + + inline Client::Client(const std::string& scheme_host_port, + const std::string& client_cert_path, + const std::string& client_key_path) + { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) + { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") + { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } + else + { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } + else + { + // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress) + // if port param below changes. + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } + } // namespace detail + + inline Client::Client(const std::string& host, int port) : + cli_(detail::make_unique(host, port)) + { + } + + inline Client::Client(const std::string& host, int port, + const std::string& client_cert_path, + const std::string& client_key_path) : + cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) + { + } + + inline Client::~Client() = default; + + inline bool Client::is_valid() const + { + return cli_ != nullptr && cli_->is_valid(); + } + + inline Result Client::Get(const std::string& path) { return cli_->Get(path); } + + inline Result Client::Get(const std::string& path, const Headers& headers) + { + return cli_->Get(path, headers); + } + + inline Result Client::Get(const std::string& path, Progress progress) + { + return cli_->Get(path, std::move(progress)); + } + + inline Result Client::Get(const std::string& path, const Headers& headers, + Progress progress) + { + return cli_->Get(path, headers, std::move(progress)); + } + + inline Result Client::Get(const std::string& path, + ContentReceiver content_receiver) + { + return cli_->Get(path, std::move(content_receiver)); + } + + inline Result Client::Get(const std::string& path, const Headers& headers, + ContentReceiver content_receiver) + { + return cli_->Get(path, headers, std::move(content_receiver)); + } + + inline Result Client::Get(const std::string& path, + ContentReceiver content_receiver, Progress progress) + { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); + } + + inline Result Client::Get(const std::string& path, const Headers& headers, + ContentReceiver content_receiver, Progress progress) + { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); + } + + inline Result Client::Get(const std::string& path, + ResponseHandler response_handler, + ContentReceiver content_receiver) + { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); + } + + inline Result Client::Get(const std::string& path, const Headers& headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) + { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); + } + + inline Result Client::Get(const std::string& path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) + { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + inline Result Client::Get(const std::string& path, const Headers& headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) + { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + inline Result Client::Get(const std::string& path, const Params& params, + const Headers& headers, Progress progress) + { + return cli_->Get(path, params, headers, std::move(progress)); + } + + inline Result Client::Get(const std::string& path, const Params& params, + const Headers& headers, + ContentReceiver content_receiver, Progress progress) + { + return cli_->Get(path, params, headers, std::move(content_receiver), + std::move(progress)); + } + + inline Result Client::Get(const std::string& path, const Params& params, + const Headers& headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) + { + return cli_->Get(path, params, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + inline Result Client::Head(const std::string& path) { return cli_->Head(path); } + + inline Result Client::Head(const std::string& path, const Headers& headers) + { + return cli_->Head(path, headers); + } + + inline Result Client::Post(const std::string& path) { return cli_->Post(path); } + + inline Result Client::Post(const std::string& path, const Headers& headers) + { + return cli_->Post(path, headers); + } + + inline Result Client::Post(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type) + { + return cli_->Post(path, body, content_length, content_type); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type) + { + return cli_->Post(path, headers, body, content_length, content_type); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, Progress progress) + { + return cli_->Post(path, headers, body, content_length, content_type, + progress); + } + + inline Result Client::Post(const std::string& path, const std::string& body, + const std::string& content_type) + { + return cli_->Post(path, body, content_type); + } + + inline Result Client::Post(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress) + { + return cli_->Post(path, body, content_type, progress); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type) + { + return cli_->Post(path, headers, body, content_type); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type, Progress progress) + { + return cli_->Post(path, headers, body, content_type, progress); + } + + inline Result Client::Post(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); + } + + inline Result Client::Post(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return cli_->Post(path, std::move(content_provider), content_type); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return cli_->Post(path, headers, std::move(content_provider), content_type); + } + + inline Result Client::Post(const std::string& path, const Params& params) + { + return cli_->Post(path, params); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + const Params& params) + { + return cli_->Post(path, headers, params); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + const Params& params, Progress progress) + { + return cli_->Post(path, headers, params, progress); + } + + inline Result Client::Post(const std::string& path, + const MultipartFormDataItems& items) + { + return cli_->Post(path, items); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items) + { + return cli_->Post(path, headers, items); + } + + inline Result Client::Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const std::string& boundary) + { + return cli_->Post(path, headers, items, boundary); + } + + inline Result + Client::Post(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items) + { + return cli_->Post(path, headers, items, provider_items); + } + + inline Result Client::Put(const std::string& path) { return cli_->Put(path); } + + inline Result Client::Put(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type) + { + return cli_->Put(path, body, content_length, content_type); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type) + { + return cli_->Put(path, headers, body, content_length, content_type); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, Progress progress) + { + return cli_->Put(path, headers, body, content_length, content_type, progress); + } + + inline Result Client::Put(const std::string& path, const std::string& body, + const std::string& content_type) + { + return cli_->Put(path, body, content_type); + } + + inline Result Client::Put(const std::string& path, const std::string& body, + const std::string& content_type, Progress progress) + { + return cli_->Put(path, body, content_type, progress); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type) + { + return cli_->Put(path, headers, body, content_type); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type, Progress progress) + { + return cli_->Put(path, headers, body, content_type, progress); + } + + inline Result Client::Put(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); + } + + inline Result Client::Put(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return cli_->Put(path, std::move(content_provider), content_type); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return cli_->Put(path, headers, std::move(content_provider), content_type); + } + + inline Result Client::Put(const std::string& path, const Params& params) + { + return cli_->Put(path, params); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + const Params& params) + { + return cli_->Put(path, headers, params); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + const Params& params, Progress progress) + { + return cli_->Put(path, headers, params, progress); + } + + inline Result Client::Put(const std::string& path, + const MultipartFormDataItems& items) + { + return cli_->Put(path, items); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items) + { + return cli_->Put(path, headers, items); + } + + inline Result Client::Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const std::string& boundary) + { + return cli_->Put(path, headers, items, boundary); + } + + inline Result + Client::Put(const std::string& path, const Headers& headers, + const MultipartFormDataItems& items, + const MultipartFormDataProviderItems& provider_items) + { + return cli_->Put(path, headers, items, provider_items); + } + + inline Result Client::Patch(const std::string& path) + { + return cli_->Patch(path); + } + + inline Result Client::Patch(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type) + { + return cli_->Patch(path, body, content_length, content_type); + } + + inline Result Client::Patch(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type, + Progress progress) + { + return cli_->Patch(path, body, content_length, content_type, progress); + } + + inline Result Client::Patch(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type) + { + return cli_->Patch(path, headers, body, content_length, content_type); + } + + inline Result Client::Patch(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, + Progress progress) + { + return cli_->Patch(path, headers, body, content_length, content_type, + progress); + } + + inline Result Client::Patch(const std::string& path, const std::string& body, + const std::string& content_type) + { + return cli_->Patch(path, body, content_type); + } + + inline Result Client::Patch(const std::string& path, const std::string& body, + const std::string& content_type, + Progress progress) + { + return cli_->Patch(path, body, content_type, progress); + } + + inline Result Client::Patch(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type) + { + return cli_->Patch(path, headers, body, content_type); + } + + inline Result Client::Patch(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type, + Progress progress) + { + return cli_->Patch(path, headers, body, content_type, progress); + } + + inline Result Client::Patch(const std::string& path, size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); + } + + inline Result Client::Patch(const std::string& path, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return cli_->Patch(path, std::move(content_provider), content_type); + } + + inline Result Client::Patch(const std::string& path, const Headers& headers, + size_t content_length, + ContentProvider content_provider, + const std::string& content_type) + { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); + } + + inline Result Client::Patch(const std::string& path, const Headers& headers, + ContentProviderWithoutLength content_provider, + const std::string& content_type) + { + return cli_->Patch(path, headers, std::move(content_provider), content_type); + } + + inline Result Client::Delete(const std::string& path) + { + return cli_->Delete(path); + } + + inline Result Client::Delete(const std::string& path, const Headers& headers) + { + return cli_->Delete(path, headers); + } + + inline Result Client::Delete(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type) + { + return cli_->Delete(path, body, content_length, content_type); + } + + inline Result Client::Delete(const std::string& path, const char* body, + size_t content_length, + const std::string& content_type, + Progress progress) + { + return cli_->Delete(path, body, content_length, content_type, progress); + } + + inline Result Client::Delete(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type) + { + return cli_->Delete(path, headers, body, content_length, content_type); + } + + inline Result Client::Delete(const std::string& path, const Headers& headers, + const char* body, size_t content_length, + const std::string& content_type, + Progress progress) + { + return cli_->Delete(path, headers, body, content_length, content_type, + progress); + } + + inline Result Client::Delete(const std::string& path, const std::string& body, + const std::string& content_type) + { + return cli_->Delete(path, body, content_type); + } + + inline Result Client::Delete(const std::string& path, const std::string& body, + const std::string& content_type, + Progress progress) + { + return cli_->Delete(path, body, content_type, progress); + } + + inline Result Client::Delete(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type) + { + return cli_->Delete(path, headers, body, content_type); + } + + inline Result Client::Delete(const std::string& path, const Headers& headers, + const std::string& body, + const std::string& content_type, + Progress progress) + { + return cli_->Delete(path, headers, body, content_type, progress); + } + + inline Result Client::Options(const std::string& path) + { + return cli_->Options(path); + } + + inline Result Client::Options(const std::string& path, const Headers& headers) + { + return cli_->Options(path, headers); + } + + inline bool Client::send(Request& req, Response& res, Error& error) + { + return cli_->send(req, res, error); + } + + inline Result Client::send(const Request& req) { return cli_->send(req); } + + inline void Client::stop() { cli_->stop(); } + + inline std::string Client::host() const { return cli_->host(); } + + inline int Client::port() const { return cli_->port(); } + + inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + + inline socket_t Client::socket() const { return cli_->socket(); } + + inline void + Client::set_hostname_addr_map(std::map addr_map) + { + cli_->set_hostname_addr_map(std::move(addr_map)); + } + + inline void Client::set_default_headers(Headers headers) + { + cli_->set_default_headers(std::move(headers)); + } + + inline void Client::set_header_writer( + std::function const& writer) + { + cli_->set_header_writer(writer); + } + + inline void Client::set_address_family(int family) + { + cli_->set_address_family(family); + } + + inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + + inline void Client::set_socket_options(SocketOptions socket_options) + { + cli_->set_socket_options(std::move(socket_options)); + } + + inline void Client::set_connection_timeout(time_t sec, time_t usec) + { + cli_->set_connection_timeout(sec, usec); + } + + inline void Client::set_read_timeout(time_t sec, time_t usec) + { + cli_->set_read_timeout(sec, usec); + } + + inline void Client::set_write_timeout(time_t sec, time_t usec) + { + cli_->set_write_timeout(sec, usec); + } + + inline void Client::set_basic_auth(const std::string& username, + const std::string& password) + { + cli_->set_basic_auth(username, password); + } + + inline void Client::set_bearer_token_auth(const std::string& token) + { + cli_->set_bearer_token_auth(token); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + + inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } + + inline void Client::set_follow_location(bool on) + { + cli_->set_follow_location(on); + } + + inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + + inline void Client::set_compress(bool on) { cli_->set_compress(on); } + + inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + + inline void Client::set_interface(const std::string& intf) + { + cli_->set_interface(intf); + } + + inline void Client::set_proxy(const std::string& host, int port) + { + cli_->set_proxy(host, port); + } + + inline void Client::set_proxy_basic_auth(const std::string& username, + const std::string& password) + { + cli_->set_proxy_basic_auth(username, password); + } + + inline void Client::set_proxy_bearer_token_auth(const std::string& token) + { + cli_->set_proxy_bearer_token_auth(token); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} + +inline void Client::enable_server_hostname_verification(bool enabled) { + cli_->enable_server_hostname_verification(enabled); +} + +inline void Client::set_server_certificate_verifier( + std::function verifier) { + cli_->set_server_certificate_verifier(verifier); +} +#endif + + inline void Client::set_logger(Logger logger) + { + cli_->set_logger(std::move(logger)); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) { + set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size)); +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + + // ---------------------------------------------------------------------------- +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/module/Radarcape_Core/main.cpp b/module/Radarcape_Core/main.cpp new file mode 100644 index 0000000..64ab62b --- /dev/null +++ b/module/Radarcape_Core/main.cpp @@ -0,0 +1,9 @@ +#include "../../../../core_library/Core/Core/ThreadManager.h" +#include "Core/JSON.h" +#ifdef _build_exe +// int main(){ +// return 0; +// } + +#endif + diff --git a/module/Radarcape_Core/todo b/module/Radarcape_Core/todo new file mode 100644 index 0000000..daefad2 --- /dev/null +++ b/module/Radarcape_Core/todo @@ -0,0 +1,9 @@ + + + + +写接受格式 接受从 radar cape 传来的数据 +比对和 radarcape 数据的差异 + + + diff --git a/module/crc_check.cpp b/module/crc_check.cpp new file mode 100644 index 0000000..714956b --- /dev/null +++ b/module/crc_check.cpp @@ -0,0 +1,240 @@ +#include "Local_Server/Data_Source/Data_Source.h" + +#include "SSR/export.h" +#include +#include +#include +using namespace Psc; +int max_distance = 700; +namespace fs = std::filesystem; +class MY_File_Data_Source : public File_Data_Source { +public: + BaseLogger* logger{}; + void pre_handle(const std::string& data) override {} + void handle_mode_s(std::string& packet) override {} + size_t line_num = 0; + size_t line_num_ex = 0; + std::map air_map; + MY_File_Data_Source(const fs::path& file_path, const fs::path& rel_path) { + auto u8_string = rel_path.u8string(); + std::string t(u8_string.begin(), u8_string.end()); + auto ttt = file_path.u8string(); + std::string fp(ttt.begin(), ttt.end()); + key = fp; + std::replace(key.begin(), key.end(), '/', '_'); + std::replace(key.begin(), key.end(), '\\', '_'); + auto path = get_exe_dir() + "/crc_check/" + t; + this->logger = new BaseLogger(path, 100 * 100 * 10000, 1); + this->key = key; + this->data_type = File_Data_Type::BIN_Blank_One_Line_With_Escape; + this->file_path = fp; + this->play_mode = Play_Mode::analysis; + this->open(); + } + + + void handle() { + std::string buffer; + auto mode_data = read(); + while (!mode_data.empty()) { + line_num_ex++; + Binary_Format_handle_buffer(buffer, mode_data, [&](std::string& packet) { + + if (packet[0] != 0x1a) { + std::cout << "first:" << mem2hex(packet) << std::endl; + return; + } + std::string error_len = key + " " + LOG_POS_SIMPLE + " mode_s_error_length"; + auto type = packet[1]; + if (type == Msg::AC) { + if (packet.size() != Msg::AC_len) { + logger->c_debug(error_len, {}, + std::to_string(type) + " size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet) + " should:" + std::to_string(Msg::AC_len)); + return; + } + } else if (type == Msg::S7) { + if (packet.size() != Mode_S_Msg::S7_len) { + logger->c_debug(error_len, {}, + std::to_string(type) + " size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet) + " should:" + std::to_string(Msg::S7_len)); + return; + } + } else if (type == Msg::S14) { + if (packet.size() != Msg::S14_len) { + logger->c_debug(error_len, {}, + std::to_string(type) + " size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet) + " should:" + std::to_string(Msg::S14_len)); + return; + } + } else if (type == Msg::Radarcape_status) { + return; // 这个报文不会解析 + if (packet.size() != Msg::Radarcape_status_len) { + logger->c_debug(error_len, {}, + std::to_string(type) + " size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet) + " should:" + std::to_string(Msg::Radarcape_status_len)); + return; + } + } else if (type == Msg::HULC_Status) { + if (packet.size() != Msg::HULC_len) { + logger->c_debug(error_len, {}, + std::to_string(type) + " HULC size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet)); + return; + } + } else { + // std::cout << "unknown_type:" << mem2hex(packet); + rotating_log("unknown_type", mem2hex(packet)); + return; + } + bool mode_s = type == Msg::S7 || type == Msg::S14; + + if (mode_s) { + line_num++; + auto msg = std::make_shared(shared_from_this(), packet); + std::optional base_station_pos; + Mode_S::parse_mode_s_bin(this, msg, base_station_pos); + } + }); + mode_data = read(); + } + this->close(); + std::cout << "line_num: " << line_num << std::endl; + std::cout << "line_num_ex: " << line_num_ex << std::endl; + } +}; + +// "C:/wyc/CLionProjects/plantSunCat/mainProjects/Radarcape/data/pos/110.txt" +void handle(const fs::path& file_path, const fs::path& rel_path) { + auto fs = std::make_shared(file_path, rel_path); + + + Mode_S::parse_ok_json = [&](Aircraft_Info* aircraft_info, Mode_S::P_S mode_s_msg, Psc::JSON json) { + + Log_Type lt({}, { + {"line:", std::to_string( fs->line_num) }, + {"df", to_string((int)(mode_s_msg->df))}, + {"icao", mode_s_msg->icao}, + // {"icao_size", std::to_string(mode_s_msg->icao.size())}, + {"hex", mode_s_msg->msg_hex}, + + //{"packet", mem2hex(mode_s_msg->packet)}, + {"packet22222222222", mem2hex(packet_to_escape_format(mode_s_msg->packet), true, " ")}, + }); + + + + // fs->logger->debug("", lt, "crc ok"); + }; + + Mode_S::parse_call_back = [&](Mode_S::Parse_Call_Back_Type type, + Aircraft_Info* aircraft_info, + const std::string& field_name_or_error_info, + std::vector bds_list, + void* cur_parser, Mode_S::P_S mode_s_msg + ) { + + if (type == Mode_S::Parse_Call_Back_Type::Normal_Block) return; + + // Log_Type lt({}, { + // {"line", std::to_string( fs->line_num) }, + // {"type", to_string(type) }, + // {"icao", mode_s_msg->icao}, + // {"hex", mode_s_msg->msg_hex}, + // {"df", to_string((int)(mode_s_msg->df))}, + // {"df", to_string(type)}, + // {"packet", mem2hex(mode_s_msg->packet)} + // }); + + auto c1 = crc_hex(mode_s_msg->msg_bin, true); + // auto c2 = crc_hex(mode_s_msg->msg_bin, false); + + + Log_Type lt({}, { + // {"line:", std::to_string( fs->line_num) }, + // {"df", to_string((int)(mode_s_msg->df))}, + // {"icao", mode_s_msg->icao}, + // {"icao_size", std::to_string(mode_s_msg->icao.size())}, + // {"hex", "\"" + mode_s_msg->msg_hex + "\","}, + + + {"data", mem2hex(packet_to_escape_format(mode_s_msg->packet), true, " ")}, + + {"hex", mode_s_msg->msg_hex }, + {"c1", c1}, + + //{"packet", mem2hex(mode_s_msg->packet)}, + }); + if (type == Mode_S::Parse_Call_Back_Type::CRC_Error) { + if (mode_s_msg->df == Downlink_Format::Extended_Squitter_17) { + auto tc = type_code(mode_s_msg->msg_bin); + lt.set("tc", std::to_string(tc)); + } + + + fs->logger->debug("", lt, ""); + } + + }; + fs->handle(); +} + +void list_directory(const fs::path& dir_path, const fs::path& base_path) { + for (const auto& entry : fs::directory_iterator(dir_path)) { + fs::path file_path = entry.path(); + auto relative_path = fs::relative(file_path, base_path); // 计算相对路径 + if (fs::is_directory(entry.status())) { + list_directory(file_path, base_path); // 递归遍历子目录 + } else { + std::cout << "Full path: " << (const char*)file_path.u8string().c_str() << std::endl; // 输出完整路径 + std::cout << "Relative path: " << (const char*)relative_path.u8string().c_str() << std::endl; // 输出相对路径 + handle(file_path, relative_path); + } + } +} +std::wstring _transformString2Wstring(const std::string& s) { + setlocale(LC_CTYPE, "en_US.UTF-8"); + const char* _Source = s.c_str(); + size_t len = strlen(_Source) + 1; + size_t converted = 0; + wchar_t* wStr = new wchar_t[len]; + mbstowcs_s(&converted, wStr, len, _Source, _TRUNCATE); + std::wstring result(wStr); + delete[] wStr; + return result; +} +std::wstring gbk_to_wstring(const std::string& gbk) { + int len = MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, nullptr, 0); + std::wstring wstr(len, L'\0'); + MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, &wstr[0], len); + // 去掉末尾'\0' + if (!wstr.empty() && wstr.back() == L'\0') wstr.pop_back(); + return wstr; +} +int main(int argc, char* argv[]) { + // 检查命令行参数是否提供了目录路径 + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + set_console_utf8(); + Base_Logger_Manager::instance().init(); + auto path = Psc::get_exe_dir() + "/crc_check/mode_s.log"; + + + + mode_s_logger = new BaseLogger(path, 100 * 100 * 10000, 1); + std::string aaa = argv[1]; + std::u8string dp((char8_t*)platform_2_utf8(aaa).data()); + auto dir_path = std::filesystem::path(dp); + if (!fs::exists(dir_path)) { + std::cerr << "!fs::exists(pa)." << std::endl; + return 1; + } + if (!fs::is_directory(dir_path)) { + std::cerr << "!fs::is_directory(pa)." << std::endl; + return 1; + } + list_directory(dir_path, dir_path); + return 0; +} diff --git a/module/dll_source/Dll_Global.cpp b/module/dll_source/Dll_Global.cpp new file mode 100644 index 0000000..bbdf5f5 --- /dev/null +++ b/module/dll_source/Dll_Global.cpp @@ -0,0 +1,266 @@ +#include "Dll_Global.h" + +#include "../../../../core_library/Core/Core/Base/JSON.h" +#include "Core/Base/SM_RingBuffer.h" + +Dll_Global *dg = new Dll_Global(); + +std::string Dll_Global::get_config_path() { return get_exe_dir() + "/" + "data_progress_config.json"; } + +void Dll_Global::init_when_exe() { + rb = new SM_RingBuffer; + rb->init("rb", 2112 * 1000); + json_func_call = new SM_RingBuffer; + json_func_call->init("json_func_call2", 1000); + mode_acs = new SM_RingBuffer; + mode_acs->init("mode_acs", 2112 * 1000); + + + void* lib; + { + auto r = try_load_library(Psc::get_abs_path(library_path)); + if (!r) { + Psc::fail_fast(); + } + lib = r.value(); + } + + { + auto r = Psc::try_load_function(lib, "adsb_set_iq_cbk"); + if (!r) { + Psc::fail_fast(); + } + set_iq_cbk = (adsb_set_iq_cbk_t)r.value(); + } + { + auto r = Psc::try_load_function(lib, "adsb_set_less_30Mhz_ddc_param"); + if (!r) { + Psc::fail_fast(); + } + set_less_30Mhz_ddc_param = (adsb_set_less_30Mhz_ddc_param_t)r.value(); + } + { + auto r = Psc::try_load_function(lib, "adsb_set_less_30Mhz_fft_param"); + if (!r) { + Psc::fail_fast(); + } + set_less_30Mhz_fft_param = (adsb_set_less_30Mhz_fft_param_t)r.value(); + } + + + + std::cout << "set_less_30Mhz_ddc_param " << VAR_STR_3(center_freq, sample_rate, band_width) << std::endl; + set_less_30Mhz_ddc_param(center_freq, sample_rate, band_width, 1); + std::cout << "set_less_30Mhz_fft_param " << VAR_STR_3(center_freq, sample_rate, band_width) << std::endl; + set_less_30Mhz_fft_param(fft_win_type, fft_point_number, fft_period_ms, 1); + std::cout << "set_iq_cbk " << std::endl; + set_iq_cbk( + [](void *phandle, void *pdata, uint64_t length) { + if (length != 2112) { + std::cout << LOG_POS << " IQ回调传入的长度不对!" << std::endl; + return; + } + auto dg = (Dll_Global *) phandle; + // + dg->handle_2112(pdata); + + // auto sk = (UHD_Struct_KFFT *) pdata; + // std::cout << (int)sk->DataType << std::endl; + + //dg->iq_memory_buffer.push(pdata, length); // length 永远等于2112 + }, + this); +} +void Dll_Global::init_when_dll() { + + Detach_Thread_Manager &manager = dtm; + + manager.test_and_start_thread("Dll_Global", [&](std::atomic& run) { + std::mt19937 rng(std::random_device{}()); // 随机数引擎 + std::uniform_int_distribution dist(0 * 256, 70 * 256); + constexpr int size = 64 + 1024 * sizeof(uint16_t); + uint8_t buf[size]; + + while (run) { + int fn = fft_point_number / 1024; + auto sk = (UHD_Struct_KFFT *) buf; + + + uint16_t *fft = (uint16_t *) (buf + 64); + for (int fi = 0; fi < fn; fi++) { + sk->DataType = kd_fft; + sk->Param.FFT.SubFrame = fi; + for (int i = 0; i < 1024; ++i) { + fft[i] = dist(rng); + } + give_call_back(buf, size); + } + + + + uint64_t sleep_ms = fft_period_ms; + uint64_t num = sample_rate / sleep_ms; + uint64_t times = num / 2048; + if (times < 1) { + times = 1; + } + + sk->DataType = zd_ddc; + uint16_t *iq = (uint16_t *) (buf + 64); + for (int i = 0; i < 1024; ++i) { + auto random = dist(rng); + iq[i] = random; + } + + sk->UsefullLen = 2048; + for (int i = 0; i < times; ++i) { + give_call_back(buf, size); + } + std::this_thread::sleep_for(std::chrono::milliseconds(fft_period_ms)); + } + }); + +} + +Dll_Global::Dll_Global() { + auto config = try_parse_json_file(get_config_path()); + if (!config.has_value()) { + std::cout << "Dll_Global 配置文件加载失败!" << std::endl; + Psc::fail_fast(); + } + load(&config.value()); + psm = new std::uint8_t[65536 * 2]; +} + +Dll_Global::~Dll_Global() { + + + delete psm; + + + +} + + +void adsb_close() { + std::cout << " adsb_close()" << std::endl; + Dll_Global::instance()->dtm.stop_all_thread(); + +} + + + +void Dll_Global::save() { + std::ofstream config_file; + config_file.open(get_config_path()); + std::string json = to_json().to_json_string(); + config_file.write(json.c_str(), static_cast(json.size())); + config_file.close(); +} + +void Dll_Global::give_call_back(std::uint8_t *data, size_t len) { + recv_cbk(puser, data, len); + auto sk = (UHD_Struct_KFFT *) data; + if (sk->DataType == kd_fft) { + auto idx_max = fft_point_number/1024; + auto idx = sk->Param.FFT.SubFrame; + if (idx >= idx_max) { + std::cout << "give_call_back idx 越界" << idx << " >= " << idx_max << std::endl; + } + } + +} + +void Dll_Global::handle_2112_test(void *data) { + // 记录开始时间 + auto start = std::chrono::high_resolution_clock::now(); + + // 调用目标函数 + handle_2112_pure(data); + + // 记录结束时间 + auto end = std::chrono::high_resolution_clock::now(); + + // 计算持续时间(微秒) + auto duration = std::chrono::duration_cast(end - start); + + // 输出每次调用的时间 + std::cout << "Average time per call: " + << duration.count() << " microseconds" << std::endl; +} + +void Dll_Global::handle_2112(void *data) { + handle_2112_pure(data); + // static Frequency_Limit fl(freq); + // if (fl.test()) { + // handle_2112_test(data); + // } else { + // handle_2112_pure(data); + // } +} + +void Dll_Global::handle_2112_pure(void *data) { + auto d = (UHD_Struct_KFFT *) (data); + auto pdata = (std::uint8_t*)data + 64; + auto idx_max = fft_point_number/1024; + auto type = d->DataType; + if (type == kd_fft) { + auto idx = d->Param.FFT.SubFrame; + if (idx >= idx_max) { + std::cout << "idx 越界" << idx << " >= " << idx_max << std::endl; + } else { + memcpy( psm + idx * 2048, pdata, 2048); + } + } +#if WIN32 + else if (type == kd_ddc || type == zd_ddc) { + dg->rb->write((uint8_t *) data, 2112); + } else { + Psc::fail_fast(); + } +#else + else { + dg->rb->write((uint8_t *) data, 2112); + } +#endif + +} + + +void Dll_Global::handle_json_call() { + size_t length = 0; + uint8_t json_func_data[1000]; + json_func_call->read(json_func_data, length); + + + std::string json((const char *) json_func_data, length); + if (!json.empty()) { + auto func_call = Psc::parse_json(json); + auto method = func_call.get_string("method"); + std::cout << func_call.to_json_string() << std::endl; + + if (method == "adsb_set_less_30Mhz_ddc_param") { + center_freq = func_call.get_number("center_freq"); + sample_rate = func_call.get_number("sample_rate"); + band_width = func_call.get_number("band_width"); + auto sw = func_call.get_number("sw"); + save(); + set_less_30Mhz_ddc_param(center_freq, sample_rate, band_width, sw); + } + + if (method == "adsb_set_less_30Mhz_fft_param") { + fft_win_type = (UHD_FFTWin) func_call.get_number("fft_win_type"); + fft_point_number = func_call.get_number("fft_point_number"); + fft_period_ms = func_call.get_number("fft_period_ms"); + auto sw = func_call.get_number("sw"); + save(); + set_less_30Mhz_fft_param(fft_win_type, fft_point_number, fft_period_ms, sw); + } + + if (method == "restart_device") { + std::cout << "关闭进程, 然后会被守护进程拉起来" << std::endl; + stop_program = true; + } + } +} +void set_error_cbk(error_cbk_t cbk) {} diff --git a/module/dll_source/Dll_Global.h b/module/dll_source/Dll_Global.h new file mode 100644 index 0000000..7ac42a3 --- /dev/null +++ b/module/dll_source/Dll_Global.h @@ -0,0 +1,94 @@ +#ifndef PLANTSUNCAT_DLL_GLOBAL_H +#define PLANTSUNCAT_DLL_GLOBAL_H +#include "main.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Core/Base/global_include.h" +#include "Core/Shared_Memory/Shared_Memory.h" +#include "Core/system/export.h" +#include "../Local_Server/DSP/global.h" +#include "Core/Base/JSON.h" +#include "Core/Base/ThreadManager.h" +#include "Core/spdlog/export.h" + +namespace Psc { +class SM_RingBuffer; +} +using namespace Psc; + + +struct Memory_Buffer { + std::vector get_all() { + std::lock_guard lock(m); + std::swap(cache, data_list); + return data_list; + } + void push(void *pdata, uint64_t length) { + auto buf = pool.get(); + buf->resize(length); + std::memcpy(buf->data(), pdata, length); + { + std::lock_guard lock(m); + cache.push_back(buf); + } + } + String_Pool pool = String_Pool(2112, 8 * 1024); +protected: + std::vector data; + std::vector cache; + std::vector data_list; + Psc::Spin_Lock m; +}; + +struct BB : Base_DSP { + BB() { + + } +}; +#define freq 0.1 + +struct Dll_Global : BB, public Singleton{ + SM_RingBuffer *rb{}; + SM_RingBuffer *json_func_call{}; + SM_RingBuffer *mode_acs{}; + // Pure_Share_Memory psm = Pure_Share_Memory("fft_data", 65536); + std::uint8_t* psm; + + Memory_Buffer iq_memory_buffer; + Detach_Thread_Manager dtm; + adsb_set_iq_cbk_t set_iq_cbk; + adsb_set_less_30Mhz_ddc_param_t set_less_30Mhz_ddc_param; + adsb_set_less_30Mhz_fft_param_t set_less_30Mhz_fft_param; + static std::string get_config_path(); + void init_when_exe(); + void init_when_dll(); + Dll_Global(); + ~Dll_Global(); + + void save(); + void handle_json_call(); + iq_cbk_t recv_cbk{}; + void *puser{}; + void give_call_back(std::uint8_t* data, size_t len); + + void handle_2112(void* data); + void handle_2112_pure(void* data); + + void handle_2112_test(void* data); +}; + + + + +#endif diff --git a/module/dll_source/device_uhd_c.h b/module/dll_source/device_uhd_c.h new file mode 100644 index 0000000..8c3d2ff --- /dev/null +++ b/module/dll_source/device_uhd_c.h @@ -0,0 +1,98 @@ +#ifndef _DEVICE_UHD_C_H_ +#define _DEVICE_UHD_C_H_ + + +#include "device_uhd_struct.h" +// #include "device_uhd_cpp.h" +#include + + + + + +typedef void (*rx_data_cbk_t)(int channel, void *pdata, int length); + +extern "C" +{ + int32_t ex_declare init9002(void **phandle); + int32_t ex_declare free9002(void **phandle); + int32_t ex_declare isDevOpened(void *phandle, int32_t *result); + // int32_t ex_declare findDevices(void *phandle, uhd::device_addrs_t *addrs); + // int32_t ex_declare openDevice(void *phandle,uhd::device_addr_t addr); + int32_t ex_declare closeDevice(void *phandle); + int32_t ex_declare findDevInfos(void *phandle, char infos[1000]); + int32_t ex_declare findDevInfos2(void *phandle, char infos[1000]); + int32_t ex_declare getDevTree(void *phandle, char tree_infos[1000]); + int32_t ex_declare setDevClockSource(void *phandle, char sourceStr[1000]); + int32_t ex_declare getDevClockSource(void *phandle, char sourceStr[1000]); + int32_t ex_declare setDevInClock(void *phandle, double clockFreq); + int32_t ex_declare getDevInClockRange(void *phandle, double *min, double *max); + int32_t ex_declare getDevInClock(void *phandle, double *val); + int32_t ex_declare getDevRXChCount(void *phandle, size_t *val); + int32_t ex_declare getDevTXChCount(void *phandle, size_t *val); + int32_t ex_declare setDevParam(void *phandle, char chStr[50], UHD_ParamType paramType, double paramValue); + int32_t ex_declare getDevParam(void *phandle, char chStr[50], UHD_ParamType paramType,double *paramValue); + int32_t ex_declare getDevParamMin(void *phandle, double *val); + int32_t ex_declare getDevParamMax(void *phandle, double *val); + int32_t ex_declare setAllAntenna(void *phandle, int32_t *result); + int32_t ex_declare setDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int jcqData); + int32_t ex_declare getDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int *jcqData); + int32_t ex_declare setDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int bSwitch); + int32_t ex_declare getDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int *bSwitch); + int32_t ex_declare setDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long centerfreq, int bw, int sample); + int32_t ex_declare getDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long *centerfreq, int *bw, int *sample); + int32_t ex_declare getDevZDCenterRange(void *phandle, char chStr[50], long long &min, long long &max); + int32_t ex_declare getDevZDBandWidthRange(void *phandle, char chStr[50], int &min, int &max); + int32_t ex_declare getDevZDSampleFreqRange(void *phandle, char chStr[50], int arr[13], int *valid_len); + int32_t ex_declare setDevFFTParam(void *phandle, UHD_FFTWin fftWin, UHD_FFTLen fftLen, int fftPeriod_ms); + int32_t ex_declare getDevFFTParam(void *phandle, UHD_FFTWin *fftWin, UHD_FFTLen *fftLen, int *fftPeriod_ms); + int32_t ex_declare getDevRecvSpeed(void *phandle, int channel, double *val); + int32_t ex_declare isDevRecving(void *phandle,int channel, int32_t *result); + int32_t ex_declare startDevRecv(void *phandle, int channel, rx_data_cbk_t rx_cbk); + int32_t ex_declare stopDevRecv(void *phandle, int channel); + int32_t ex_declare getDevSendSpeed(void *phandle, int channel, double *val); + int32_t ex_declare isDevSending(void *phandle, int channel, int32_t *rusult); + int32_t ex_declare startDevSend(void *phandle, int channel, unsigned long long send_cf, unsigned long long data_sf, char *filename, int bwhile); + int32_t ex_declare stopDevSend(void *phandle, int channel); + int32_t ex_declare getGPSInfo(void *phandle, char gps_info[50]); + int32_t ex_declare setLedEnable(void *phandle, int led_num, int bEnable); + int32_t ex_declare getLedEnable(void *phandle,int led_num, int *bEnable); + + + + + typedef void(*adsb_msg_cbk_t)(void *pusr,char *pmsg,int msglen); + + int32_t ex_declare init_adsb9002(void **phandle,adsb_msg_cbk_t cbk,void *pusr); + int32_t ex_declare free_adsb9002(void **phandle); + + + + // 新接口 :时间 20251107 , + //TODO: 1 增加 dma 回调函数 iq 的(ddc,ffc) +} + +#include +#include "device_uhd_c.h" +#include +#include + + +extern "C" +{ + + + ex_declare void adsb_close(); + + typedef void (*iq_cbk_t)(void* phandle, void* pdata, uint64_t length); + + typedef int (*adsb_set_less_30Mhz_ddc_param_t)(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + typedef int (*adsb_set_less_30Mhz_fft_param_t)(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw); + ex_declare int adsb_set_iq_cbk(iq_cbk_t recv_cbk, void* pusr); + ex_declare int adsb_set_less_30Mhz_ddc_param(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + ex_declare int adsb_set_less_30Mhz_fft_param(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw); + +} + + +#endif //_DEVICE_UHD_C_H_ \ No newline at end of file diff --git a/module/dll_source/device_uhd_struct.h b/module/dll_source/device_uhd_struct.h new file mode 100644 index 0000000..c5da56f --- /dev/null +++ b/module/dll_source/device_uhd_struct.h @@ -0,0 +1,259 @@ +#ifndef device_uhd_struct_h +#define device_uhd_struct_h + +#include +#include +#include + + +#define ZDSamSum 200000000//8路窄带采样率之和 +#define ZDChMax 8 //每路大通道下窄带通道总数 +enum UHD_ParamType : int +{ + CenterFreq = 0, + BandWidth = 1, + SampleFreq = 2, + Gain = 3, + PowerRef = 4, + KDDCSwitch = 5, + KFFTSwitch = 6, + ZDDCSwitch = 7, + ZDDCParam = 8, + KFFTParam = 9, + ClockSource = 10, + SnapshotSwitch = 11, + Snapshot_tm_s = 12, +}; +enum UHD_FFTWin : unsigned char +{ + jxc =0,// 矩形窗 = 0, + hnc = 1,// 汉宁窗 = 1, + hmc = 2, // 海明窗 = 2, + blkmc = 3,// 布莱克曼窗 = 3, +}; +enum UHD_FFTLen : unsigned int +{ + _1024 = 1024, + _2048 = 2048, + _4096 = 4096, + _8192 = 8192, + _16384 = 16384, + _32768 = 32768, +}; +#pragma pack(push) +#pragma pack(1) +struct UHD_ZDParam +{ + unsigned char KCH = 0;//宽带通道号 + unsigned short ZCH = 0;//窄带通道号 + long long CenterFreq = 0; // 中频 + int BandWidth = 0; //带宽 + int SampleFreq = 0; // 采样率 + int ExtraPer = 1024;//抽取率 4-4096,2的指数 + bool bSwitch_ZDDC = false; + +}; +struct UHD_KDParam +{ + unsigned char KCH = 0;//宽带通道号 + long long CenterFreq = 10000000; + + int BandWidth = 160000000; + int SampleFreq = 200000000; + + bool bSwitch_6GCH = false; + + bool bSwitch_KDDC = false; + bool bSwitch_KFFT = false; + double gain = 0; + UHD_ZDParam ZDParam[ZDChMax]; + int ExtraPer; + bool Snapshot_Switch=false; + int Snapshot_tm_s = 0; + + unsigned long long old_sf; + unsigned long long old_cf; + unsigned long long old_bw; + +}; +#pragma region 枚举/结构体 + +/** @brief TODO: 时码类型枚举 + * @enum LibWZ57_Enum_TimeType + */ +enum Enum_TimeType : uint8_t +{ + Enum_TimeType_Invlid = 0, /**< 无效的*/ + Enum_TimeType_IRIG_B = 1, /**< IRIG-B*/ + Enum_TimeType_RMC = 2, /**< RMC*/ + Enum_TimeType_ABSCNTTM = 3, /**< 绝对计数时间*/ + Enum_TimeType_REFCNTTM = 4, /**< 相对计数时间*/ +}; +typedef struct struct_DateTime +{ + + Enum_TimeType TimeType; /**< 10 时码类型*/ + + /** @brief 10个字节的时码信息 + * @details + * 根据 @ref LibWZ57_Enum_TimeType TimeType,对应不同的 联合体 + */ + union TimeInfo + { + ///@brief IRIG-B + ///@struct IRIGB_Head + struct IRIGB_Head + { + uint8_t Year; /**< 年 字节1*/ + uint16_t Day; /**< 日 字节2*/ + uint8_t Hour; /**< 时 字节1*/ + uint8_t Minute; /**< 分 字节1*/ + uint8_t Second; /**< 秒 字节1*/ + uint32_t NanoSec; /**< 纳 字节4*/ + } IRIGB_Head; + + ///@brief RMC + ///@struct RMC_Head + struct RMC_Head + { + uint8_t Year; /**< 年 字节1*/ + uint8_t Month; /**< 月 字节1*/ + uint8_t Day; /**< 日 字节1*/ + uint8_t Hour; /**< 时 字节1*/ + uint8_t Minute; /**< 分 字节1*/ + uint8_t Second; /**< 秒 字节1*/ + uint32_t NanoSec; /**< 纳 字节4*/ + } RMC_Head; + + ///@brief 绝对计数时间 + ///@struct ABSCNTTM_Head + struct ABSCNTTM_Head + { + ///@brief 秒 字节6; 基准秒计数时间+相对秒计数时间 单位:秒 + uint8_t Second[6]; + ///@brief 纳秒 字节4; + uint32_t NanoSec; + } ABSCNTTM_Head; + + ///@brief 相对计数时间 + ///@struct REFCNTTM_Head + struct REFCNTTM_Head + { + ///@brief 秒 字节6; 相对秒计数时间单位:秒 + uint8_t Second[6]; + ///@brief 纳秒 字节4; + uint32_t NanoSec; + } REFCNTTM_Head; + } TimeInfo; + +} struct_DataTime; + + + +#pragma region 枚举 +/***************************** +*数据类型 +*****************************/ +enum UHD_Enum_DataType : unsigned char +{ + zd_ddc = 1, + kd_ddc = 2, + kd_fft = 3, + GPS_Info = 16, + // 窄带DDC = 0x01, + // 宽带DDC = 0x02, + // 宽带FFT = 0x03, +}; +#pragma endregion +#pragma region 结构体 +#pragma region UHD_Struct_KFFT UHD_Struct_KDDC UHD_Struct_ZDDC +/// +/// 宽带FFT头 +/// +typedef struct +{ + /// + /// 帧标识 6 0x2828F628F6F6 固定值 + /// + unsigned int Head; + /// + /// + /// + unsigned short Head2; + /// + /// 协议版本 1 0x80 + /// + unsigned char DealVersion; + /// + /// 帧头长度 1 64 + /// + unsigned char FrameHeadLen; + /// + /// 数据长度 2 2048 + /// + unsigned short DataLen; + /// + /// 有效数据长度 2 2048 + /// + unsigned short UsefullLen; + /// + /// 数据类型 1 + /// + UHD_Enum_DataType DataType; + unsigned char Other1; + /// + /// 通道号 + /// + unsigned char Channel; + unsigned char Other2; + unsigned short ZDChannel; + /// + /// 帧计数 2 0 ~ 65535 循环递增 + /// + unsigned short FrameIndex; + /// + /// 帧起止标识 1 0x0 + /// + unsigned char FrameBeginFlag; + struct_DateTime DateTm; + /// + /// 采样率 4 单位Hz + /// + unsigned int SampleFreq_Hz; + /// + /// 带宽 4 单位Hz + /// + unsigned int BandWidth_Hz; + /// + /// 中心频点 4 单位Hz,低位 + /// + unsigned int CenterFreq_Hz_Low; + /// + /// 中心频点 2 单位Hz,高位 + /// + unsigned short CenterFreq_Hz_High; + union + { + unsigned char other5[18]; + struct + { + unsigned char other4[6]; + /// + /// FFT长度 65536 + /// + int FFTPoint; + unsigned char other5[2]; + float other6; + /// + /// 段内帧号 + /// + unsigned short SubFrame; + } FFT; + } Param; + //数据 2048 共1024个数据,1个数据2字节。 +} UHD_Struct_KFFT, UHD_Struct_KDDC, UHD_Struct_ZDDC; +#pragma endregion +#pragma endregion +#pragma pack(pop) +#pragma endregion +#endif diff --git a/module/dll_source/error_callback.h b/module/dll_source/error_callback.h new file mode 100644 index 0000000..5deac4d --- /dev/null +++ b/module/dll_source/error_callback.h @@ -0,0 +1,19 @@ +#ifndef __ERROR_CALLBACK_H__ +#define __ERROR_CALLBACK_H__ + +extern "C" { + typedef void (*error_cbk_t)(const char *file_name ,const char *func_name,const char *err_msg); + extern error_cbk_t g_error_callback; + + ex_declare void error_callback_call( const char *file_name ,const char *func_name,const char *err_msg); + ex_declare const char *extract_filename_in_cbk(const char *full_path); + + + ex_declare void set_error_cbk(error_cbk_t cbk); + #define MACROS_ERROR_CBK_CALL(msg) error_callback_call(__FILE__,__func__,msg) + + + typedef int (*set_error_cbk_t)(error_cbk_t cbk); +} + +#endif // __ERROR_CALLBACK_H__ diff --git a/module/dll_source/main.cpp b/module/dll_source/main.cpp new file mode 100644 index 0000000..2409921 --- /dev/null +++ b/module/dll_source/main.cpp @@ -0,0 +1,207 @@ +#include "main.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Core/Base/JSON.h" +#include "Core/Base/ThreadManager.h" +#include "Core/Base/global_include.h" +#include "Core/Shared_Memory/Shared_Memory.h" +#include "Core/system/export.h" +#include "Core/spdlog/export.h" + +#include "Core/Base/SM_RingBuffer.h" +#include "Dll_Global.h" + + +std::vector ddd = { + "1A 33 1A 1A F1 DA 08 2A 73 60 8D 78 12 0A EA 28 88 64 25 3C 08 33 22 41", + "1A 33 1A 1A F1 D9 13 EA E7 11 8D 78 1D 40 58 BF 41 E2 45 49 09 7B C8 24", + "1A 33 1A 1A F1 DA 08 2A 73 60 8D 78 12 0A EA 28 88 64 25 3C 08 33 22 41", + "1A 33 1A 1A F1 DA D4 35 F9 69 A0 00 15 BC C2 9E 14 F0 A8 00 00 DE E7 37", + "1A 33 1A 1A F1 DB 45 5D 1C 68 A0 00 15 BC FF DD 11 2B 60 04 DB 2F 06 E2", + "1A 33 1A 1A F1 DD 0F 74 3E 20 A0 00 15 BC EE 0A 13 30 20 37 FF A9 CE DC", + "1A 33 1A 1A F1 DD 50 97 59 30 8D 78 00 6C E1 1F 2E 00 00 00 00 09 43 55", + "1A 33 1A 1A F1 DD 73 6B 25 2B 8D 78 16 2D F8 33 00 06 00 49 B8 CF B0 D7", + "1A 33 1A 1A F1 DD F1 C2 C2 1D A0 00 15 BC 10 03 0A 80 ED 00 00 98 54 C8", + "1A 33 1A 1A F1 DE 62 E9 B6 2A A0 00 15 BC 10 03 0A 80 ED 00 00 98 54 C8", + "1A 33 1A 1A F1 DE B4 E1 1E 23 8D 79 A0 53 EA 3E C8 66 57 3C 08 71 74 65", + "1A 33 1A 1A F1 DF 17 8E 3F 5D 8D 78 00 6C 58 63 44 5B 32 CB 60 C5 DD 2E", + "1A 33 1A 1A F1 DF 3E 49 FD 76 A0 00 0E 3E A8 7A 23 26 E1 0C 22 4E D9 88", + "1A 33 1A 1A F1 DF AF 71 5F 6C A0 00 0E 3E A8 7A 23 26 E1 0C 22 4E D9 88", + "1A 33 1A 1A F1 E2 0A DD 76 2A A0 00 11 B1 B5 E8 00 30 AA 00 00 D5 D0 A3", +}; + + +std::string get_absb() { + static size_t index = 0; // 静态变量用于保存当前读取位置 + index++; + if (index == ddd.size()) { + index = 0; + } + return ddd[index]; +} + +class TextLineReader { +public: + explicit TextLineReader(const std::string& file_path) { + load(file_path); + } + + // 每调用一次,返回一行 + // 返回 true 表示成功取到一行 + // 返回 false 表示已经没有更多行 + bool get(std::string& ret_line) { + if (index_ >= lines_.size()) { + return false; + } + + ret_line = lines_[index_]; + ++index_; + return true; + } + + // 重新从第一行开始读 + void reset() { + index_ = 0; + } + + // 总行数 + std::size_t size() const { + return lines_.size(); + } + + // 是否已经读完 + bool empty() const { + return lines_.empty(); + } + +private: + void load(const std::string& file_path) { + std::ifstream file(file_path); + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + file_path); + } + + std::string line; + while (std::getline(file, line)) { + lines_.push_back(line); + } + + index_ = 0; + } + +private: + std::vector lines_; + std::size_t index_ = 0; +}; + + + + +std::size_t adsb_read(char *buf, std::size_t len) { + + + static TextLineReader reader(get_exe_dir() + "/ADS-B_195_0205.txt"); + std::string line; + reader.get(line); + auto l = line.size(); + std::memcpy(buf, line.c_str(), l); + return l; +} + +int adsb_set_iq_cbk(iq_cbk_t recv_cbk, void *pusr) { + auto dg = Dll_Global::instance(); + dg->recv_cbk = recv_cbk; + dg->puser = pusr; + + + + dg->init_when_dll(); + return 0; +} +int adsb_set_less_30Mhz_ddc_param(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw) { + auto dg = Dll_Global::instance(); + dg->center_freq = cf_hz; + dg->sample_rate = sf_hz; // 设置采样率 + dg->band_width = bw_hz; + return 0; +} +int adsb_set_less_30Mhz_fft_param(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw) { + auto dg = Dll_Global::instance(); + dg->fft_point_number = fftLen; + dg->fft_period_ms = fftPeriod_ms; + dg->fft_win_type = (UHD_FFTWin)fftWin; + return 0; +} + + + + + +int main() { + init_signal_config(); + auto dg = Dll_Global::instance(); + + + dg->init_when_exe(); + bool debug = false; + + auto mb = get_system_memory()/(1024*1024); + auto gb = mb/(1024); + std::cout << "当前系统的物理内存大小" << mb << " MB" << std::endl; + std::cout << "当前系统的物理内存大小" << gb << " GB" << std::endl; + + dg->dtm.test_and_start_thread("dsp共享内存数据推送", [dg](std::atomic & running) { + bool debug = true; + while (running) { + auto list = dg->iq_memory_buffer.get_all(); + // 释放内存池 + Pool_Guard pg(&dg->iq_memory_buffer.pool, list); + + if (debug && list.size() > 1000) { + static Frequency_Limit fl(1.0/60.0); + std::ostringstream oss; + oss << get_current_millisecond_timestamp() << " 警告: dsp共享内存数据推送数据过多 当前数据队列长度:" << list.size() << std::endl; + if (fl.test()) { + std::cout << oss.str() << std::endl; + } + } + if (list.empty()) { + std::this_thread::sleep_for(std::chrono::microseconds(1)); + continue; + } + auto frame_number = list.size(); + static Frequency_Limit fl(freq); + if (fl.test()) { + + std::cout << dg->rb->state_str() << " dsp数据推送" << frame_number << "条" << std::endl; + } + + for (int i = 0; i < frame_number; i++) { + auto buf = list[i]; + dg->handle_2112(buf->data()); + } + } + }); + while (!stop_program) { + dg->handle_json_call(); + + } + if (stop_program == SIGINT) { + std::cout << "开始关闭所有子线程!" << std::endl; + + } + return 0; +} + + diff --git a/module/dll_source/main.h b/module/dll_source/main.h new file mode 100644 index 0000000..119a31b --- /dev/null +++ b/module/dll_source/main.h @@ -0,0 +1,50 @@ +#pragma once + +#define MY_LIBRARY_EXPORTS +#ifdef _WIN32 // Windows 平台 +#ifdef MY_LIBRARY_EXPORTS // 如果正在构建库 +#define ex_declare __declspec(dllexport) // 导出符号 +#else +#define ex_declare __declspec(dllimport) // 导入符号 +#endif +#else // 对于 Linux 和其他 Unix-like 系统 +#define ex_declare __attribute__((visibility("default"))) // 导出符号 +#endif +#include +#include "./error_callback.h" +#include "./device_uhd_c.h" + +extern "C" { + ex_declare void adsb_close(); + ex_declare std::size_t adsb_read(char* buf, std::size_t len); + + // IQ 数据接口 + typedef void (*iq_cbk_t)(void* phandle, void* pdata, uint64_t length); + typedef int (*adsb_set_iq_cbk_t)(iq_cbk_t recv_cbk, void* pusr); + typedef int (*adsb_set_less_30Mhz_ddc_param_t)(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + typedef int (*adsb_set_less_30Mhz_fft_param_t)(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw); + ex_declare int adsb_set_iq_cbk(iq_cbk_t recv_cbk, void* pusr); + ex_declare int adsb_set_less_30Mhz_ddc_param(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + ex_declare int adsb_set_less_30Mhz_fft_param(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw); + + + + + int32_t ex_declare init_adsb9002(void **phandle, adsb_msg_cbk_t cbk, void *pusr); + int32_t ex_declare free_adsb9002(void **phandle); + int ex_declare set_iq_cbk(void *phandle, iq_cbk_t recv_cbk, void *pusr); + int ex_declare set_less_30Mhz_ddc_param(void *phandle, uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + int ex_declare set_less_30Mhz_fft_param(void *phandle, int fftWin, int fftLen, int fftPeriod_ms, int8_t sw); + + + + + + + using init_adsb9002_ptr_t = int32_t (*)(void **phandle, adsb_msg_cbk_t cbk, void *pusr); + using free_adsb9002_ptr_t = int32_t (*)(void **phandle); + using set_iq_cbk_ptr_t = int (*)(void *phandle, iq_cbk_t recv_cbk, void *pusr); + using set_less_30Mhz_ddc_param_ptr_t = int (*)(void *phandle, uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + using set_less_30Mhz_fft_param_ptr_t = int (*)(void *phandle, int fftWin, int fftLen, int fftPeriod_ms, int8_t sw); +} + diff --git a/module/dll_source_lzy/114/out.tar b/module/dll_source_lzy/114/out.tar new file mode 100644 index 0000000000000000000000000000000000000000..b05f3ea364c2936cb00bdf617de1dd758ee575f7 GIT binary patch literal 1034240 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�d$N< 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 Z6e}ywJRZ_noKhT@XF+Nn4*?!c{|1!c|6Kq8 literal 0 HcmV?d00001 diff --git a/module/dll_source_lzy/114/out/lib/.idea/.gitignore b/module/dll_source_lzy/114/out/lib/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/module/dll_source_lzy/114/out/lib/.idea/deployment.xml b/module/dll_source_lzy/114/out/lib/.idea/deployment.xml new file mode 100644 index 0000000..d88a5d7 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/.idea/deployment.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/.idea/editor.xml b/module/dll_source_lzy/114/out/lib/.idea/editor.xml new file mode 100644 index 0000000..9a0064f --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/.idea/editor.xml @@ -0,0 +1,335 @@ + + + + + \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/.idea/lib.iml b/module/dll_source_lzy/114/out/lib/.idea/lib.iml new file mode 100644 index 0000000..bc2cd87 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/.idea/lib.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/.idea/misc.xml b/module/dll_source_lzy/114/out/lib/.idea/misc.xml new file mode 100644 index 0000000..369389b --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/.idea/modules.xml b/module/dll_source_lzy/114/out/lib/.idea/modules.xml new file mode 100644 index 0000000..b0c4ff6 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/DDCSampTable.h b/module/dll_source_lzy/114/out/lib/DDCSampTable.h new file mode 100644 index 0000000..264626e --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/DDCSampTable.h @@ -0,0 +1,28 @@ +#ifndef DDCSAMPTABLE_H +#define DDCSAMPTABLE_H +#include +using namespace std; +// adsb +const double KDDCSampTab0[11][2] = {{24000000, 1}, {12000000, 2}, {6000000, 4}, {3000000, 8}, {1500000, 16}, {750000, 32}, {375000, 64}, {187500, 128}, {93750, 256}, {46875, 512}, {23437, 1024}}; +const string KDDCSampTabStr0[11] = {"24000000 ", "12000000 ", "6000000 ", "3000000 ", "1500000 ", "750000 ", "375000 ", "187500 ", "93750 ", "46875 ", "23437 "}; +const int KDDCSampIndxMin0 = 0; +const int KDDCSampIndxMax0 = 10; +const double define_KDDCSampleMax0 = 24000000; +const double X0 = 0.8; +const int ZDDCSampIndxMin0 = 0; +const int ZDDCSampIndxMax0 = 12; +const double ZDDCSampTab0[13] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; +const double TXSFTab0[11] = {24000000, 12000000, 6000000, 3000000, 1500000, 750000, 375000, 187500, 93750, 46875, 23437}; + +// less 30 mhz +const double KDDCSampTab1[11][2] = {{61440000, 1}, {30720000, 2}, {15360000, 4}, {7680000, 8}, {3840000, 16}, {1920000, 32}, {960000, 64}, {480000, 128}, {240000, 256}, {120000, 512}, {60000, 1024}}; +const string KDDCSampTabStr1[11] = {"61440000", "30720000", "15360000", "7680000", "3840000", "1920000", "960000", "480000", "240000", "120000", "60000"}; +const int KDDCSampIndxMin1 = 0; +const int KDDCSampIndxMax1 = 10; +const double define_KDDCSampleMax1 = 61440000; +const double X1 = 0.8; +const int ZDDCSampIndxMin1 = 0; +const int ZDDCSampIndxMax1 = 12; +const double ZDDCSampTab1[13] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; +const double TXSFTab1[11] = {61440000, 30720000, 15360000, 7680000, 3840000, 1920000, 960000, 480000, 240000, 120000, 60000}; +#endif // DDCSAMPTABLE_H diff --git a/module/dll_source_lzy/114/out/lib/FIRGenLowpass_no_ipp.h b/module/dll_source_lzy/114/out/lib/FIRGenLowpass_no_ipp.h new file mode 100644 index 0000000..717bd54 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/FIRGenLowpass_no_ipp.h @@ -0,0 +1,105 @@ +#ifndef _FIRGenLowpass_H_ +#define _FIRGenLowpass_H_ +// #include "ipp/ipps.h" +#include +#include +#define FIRGenLowpassLen 256 //滤波器长度 +#define FIRGenLowpassLen2 192 //滤波器长度 + double m_dFilterCoef[FIRGenLowpassLen]; //滤波器系数 +unsigned char m_dFilterBuff[FIRGenLowpassLen*2]; +#define PI (3.1415926) +#define POINT_SIZE (1024) +//int m_nFIRPnt; //????? +double m_dFIRCoef_ptr[POINT_SIZE]; //????? +float m_fFIRCoef_ptr[POINT_SIZE]; //????? +bool GenLowpass(double rFreq, double **coef, int point,float mult) +{ + if (NULL == *coef) + { + *coef =(double *) m_fFIRCoef_ptr; + } + int nFIRPnt0 = point - 1; + float fFIRPnt0D2 = nFIRPnt0 / 2.0f; + for (int i = 0; i < point; i++) + { + m_dFIRCoef_ptr[i] = 0.54 - 0.46 * cos((2 * PI * i) / nFIRPnt0); + } + double dTheta = 2 * PI * rFreq, dCoefSum = 0; + + for (int i = 0; i < point; i++) + { + if (fFIRPnt0D2 == i) + { + m_dFIRCoef_ptr[i] *= (dTheta / PI); + } + else + { + m_dFIRCoef_ptr[i] *= (sin((i - fFIRPnt0D2) * dTheta) / ((i - fFIRPnt0D2) * PI)); + } + dCoefSum += m_dFIRCoef_ptr[i]; + } + mult /= dCoefSum; + for(int i = 0; i < point; ++i) + { + (*coef)[i] = m_dFIRCoef_ptr[i] * mult; + } + return true; +} +/***************************** +*函数名:SetFIRLpCoef +*功能描述:设置滤波器参数 +*参数名称:coef 滤波器系数 + length 滤波器系数长度 + rate 带宽采样率之比 = 带宽 / 采样率 + bitLen 量化位宽 + mult 带宽放大系数 +*返回:成功有效滤波器长度, 失败返回0 +*****************************/ +int SetFIRLpCoef2(int *coef, int length, double rate, int bitLen, double mult = 1.0) +{ + int ret = 0; + if (NULL == coef) + { + return ret; + } + double* dFIRCoef = new double[length]; + std::fill(dFIRCoef, dFIRCoef + length, 0); + if (!GenLowpass(rate * 0.5 * mult, &dFIRCoef, length, (float ) pow(2.0, bitLen - 1) - 1)) + { + ret = 0; + } + if (bitLen <= 16) + { + for (int i = 0; i < length; ++i) { + if (dFIRCoef[i] >= 0) + { + ((short *)coef)[i] = (short)(dFIRCoef[i] + 0.5); + } + else + { + ((short *)coef)[i] = (short)(dFIRCoef[i] - 0.5); + } + } + ret = length / 2; + } + else + { + for (int i = 0; i < length; ++i) { + if (dFIRCoef[i] >= 0) + { + coef[i] = (int)(dFIRCoef[i] + 0.5); + } + else + { + coef[i] = (int)(dFIRCoef[i] - 0.5); + } + } + ret = length; + } + if (dFIRCoef != nullptr) + { + delete[] dFIRCoef; + } + return ret; +} +#endif //_FIRGenLowpass_H_ diff --git a/module/dll_source_lzy/114/out/lib/a.dat b/module/dll_source_lzy/114/out/lib/a.dat new file mode 100644 index 0000000..e69de29 diff --git a/module/dll_source_lzy/114/out/lib/cJSON.c b/module/dll_source_lzy/114/out/lib/cJSON.c new file mode 100644 index 0000000..d7c7236 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/cJSON.c @@ -0,0 +1,3164 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 18) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + item->valuestring = NULL; + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + item->string = NULL; + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +/* Note: when passing a NULL valuestring, cJSON_SetValuestring treats this as an error and return NULL */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + size_t v1_len; + size_t v2_len; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + /* return NULL if the object is corrupted or valuestring is NULL */ + if (object->valuestring == NULL || valuestring == NULL) + { + return NULL; + } + + v1_len = strlen(valuestring); + v2_len = strlen(object->valuestring); + + if (v1_len <= v2_len) + { + /* strcpy does not handle overlapping string: [X1, X2] [Y1, Y2] => X2 < Y1 or Y2 < X1 */ + if (!( valuestring + v1_len < object->valuestring || object->valuestring + v2_len < valuestring )) + { + return NULL; + } + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + output = NULL; + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + if (printed != NULL) + { + hooks->deallocate(printed); + printed = NULL; + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + p.buffer = NULL; + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + if (cannot_access_at_index(input_buffer, 1)) + { + goto fail; /* nothing comes after the comma */ + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL) || (item != parent->child && item->prev == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0 || newitem == NULL) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + if (after_inserted != array->child && after_inserted->prev == NULL) { + /* return false if after_inserted is a corrupted array item */ + return false; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse); + +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + return cJSON_Duplicate_rec(item, 0, recurse ); +} + +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + if(depth >= CJSON_CIRCULAR_LIMIT) { + goto fail; + } + newchild = cJSON_Duplicate_rec(child, depth + 1, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); + object = NULL; +} diff --git a/module/dll_source_lzy/114/out/lib/cJSON.h b/module/dll_source_lzy/114/out/lib/cJSON.h new file mode 100644 index 0000000..37520bb --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/cJSON.h @@ -0,0 +1,306 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 18 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* Limits the length of circular references can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_CIRCULAR_LIMIT +#define CJSON_CIRCULAR_LIMIT 10000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/module/dll_source_lzy/114/out/lib/device_uhd_c.cpp b/module/dll_source_lzy/114/out/lib/device_uhd_c.cpp new file mode 100644 index 0000000..18f7dd9 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/device_uhd_c.cpp @@ -0,0 +1,1804 @@ + +#include "device_uhd_c.h" +#include "CtrlSpecialDecode.h" +#include "_AlgmDemodulate.h" +#include "cJSON.h" +#include "device_uhd_cpp.h" +#include "device_uhd_struct.h" +#include "error_callback.h" +#include "hulc/DataOutputFormats.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +rx_data_cbk_t s_cbk; + +#include +#include +#include + +#include +using namespace std; + +// stringstream error_ss; +// mutex error_ss_mutex; +// device_uhd_c_error_out_cbk_t error_out_cbk; + +typedef struct config_struct_t +{ + char cwd[PATH_MAX]; + char dirpath[PATH_MAX]; + double rx_gain_val = 0; + char rx_clock_ref[50] = {0}; + + char config_name[255]; // = "lib9002config.json"; + char config_path[PATH_MAX]; + char is_save_adsb; + char is_save_yuanshi_iq; + int new_file_zhouqi_s; + int max_save_file_count; + + uint64_t save_yuanshi_size_MByte; + bool need_new_file; + + uint64_t cache_over_byte; + char is_save_gps; + + char is_trans_gps; + + int end_flag = 3; + +} config_struct_t; +ofstream ofs_adsb; +ofstream ofs_yuanshi_iq; +ofstream ofs_gps; +config_struct_t cfg; +std::mutex cfg_mutex; +std::thread *pread_cfg_thread; +bool b_read_cfg_running = false; +deque yuanshi_iq_file_path_dq; + +#include +#include +#include +#include + +void read_cfg_func() +{ + time_t read_cfg_t0 = time(0); + bool b_once = false; + while (b_read_cfg_running) + { + + time_t read_cfg_t1 = time(0); + + if (difftime(read_cfg_t1, read_cfg_t0) > cfg.new_file_zhouqi_s || b_once == false) + { + b_once = true; + { + ifstream ifs(cfg.config_path); + string str; + + if (ifs.is_open()) + { + std::stringstream ss; + ss << ifs.rdbuf(); + str = ss.str(); + ifs.close(); + } + else + { + ofstream ofs(cfg.config_path); + string tmp_str = "{\"gain\":\"30.0\",\"clock_ref\":\"external\",\"is_save_adsb\":\"0\",\"new_file_zhouqi_s\":\"60\",\"max_save_file_count\":\"3\",\"is_save_yuanshi_iq\":\"0\",\"save_yuanshi_size_MByte\":\"0\",\"is_trans_gps\":\"0\",\"is_save_gps\":\"0\"}"; + + ofs.write(tmp_str.c_str(), tmp_str.size()); + str = tmp_str; + ofs.close(); + } + + cJSON *root = nullptr; + if (str.size() > 2) + { + root = cJSON_Parse(str.data()); + } + else + { + sleep(5); + continue; + } + + cJSON *obj_gain = nullptr; + obj_gain = cJSON_GetObjectItem(root, "gain"); + if (obj_gain != nullptr) + { + lock_guard lg(cfg_mutex); + char *value = obj_gain->valuestring; + + cfg.rx_gain_val = std::stof(value); + } + else + { + cJSON_AddItemToObject(root, "gain", cJSON_CreateString("30.0")); + } + + cJSON *obj_clock_ref = nullptr; + obj_clock_ref = cJSON_GetObjectItem(root, "clock_ref"); + if (obj_clock_ref != nullptr) + { + lock_guard lg(cfg_mutex); + char *value = obj_clock_ref->valuestring; + + strcpy(cfg.rx_clock_ref, value); + } + else + { + cJSON_AddItemToObject(root, "clock_ref", cJSON_CreateString("external")); + } + + cJSON *obj_is_save_adsb = nullptr; + obj_is_save_adsb = cJSON_GetObjectItem(root, "is_save_adsb"); + if (obj_is_save_adsb != nullptr) + { + lock_guard lg(cfg_mutex); + char *value = obj_is_save_adsb->valuestring; + + cfg.is_save_adsb = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "is_save_adsb", cJSON_CreateString("0")); + } + + cJSON *obj_is_save_yuanshi_iq = nullptr; + obj_is_save_yuanshi_iq = cJSON_GetObjectItem(root, "is_save_yuanshi_iq"); + if (obj_is_save_adsb != nullptr) + { + lock_guard lg(cfg_mutex); + char *value = obj_is_save_yuanshi_iq->valuestring; + + cfg.is_save_yuanshi_iq = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "is_save_yuanshi_iq", cJSON_CreateString("0")); + } + + { + cJSON *obj_new_file_zhouqi_s = nullptr; + obj_new_file_zhouqi_s = cJSON_GetObjectItem(root, "new_file_zhouqi_s"); + if (obj_new_file_zhouqi_s != nullptr) + { + lock_guard lg(cfg_mutex); + char *value = obj_new_file_zhouqi_s->valuestring; + + cfg.new_file_zhouqi_s = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "new_file_zhouqi_s", cJSON_CreateString("60")); + } + } + + { + cJSON *obj_max_save_file_count = nullptr; + obj_max_save_file_count = cJSON_GetObjectItem(root, "max_save_file_count"); + if (obj_max_save_file_count != nullptr) + { + lock_guard lg(cfg_mutex); + char *value = obj_max_save_file_count->valuestring; + + cfg.max_save_file_count = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "max_save_file_count", cJSON_CreateString("3")); + } + } + + cJSON *obj_save_yuanshi_size_MByte = nullptr; + obj_save_yuanshi_size_MByte = cJSON_GetObjectItem(root, "save_yuanshi_size_MByte"); + if (obj_save_yuanshi_size_MByte != nullptr) + { + lock_guard lg(cfg_mutex); + char *value = obj_save_yuanshi_size_MByte->valuestring; + + cfg.save_yuanshi_size_MByte = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "save_yuanshi_size_MByte", cJSON_CreateString("0")); + } + // is_save_gps + + cJSON *obj_is_save_gps = nullptr; + obj_is_save_gps = cJSON_GetObjectItem(root, "is_save_gps"); + if (obj_is_save_gps != nullptr) + { + lock_guard lg(cfg_mutex); + char *value = obj_is_save_gps->valuestring; + + cfg.is_save_gps = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "is_save_gps", cJSON_CreateString("0")); + } + + // is_trans_gps + cJSON *obj_is_trans_gps = nullptr; + obj_is_trans_gps = cJSON_GetObjectItem(root, "is_trans_gps"); + if (obj_is_trans_gps != nullptr) + { + lock_guard lg(cfg_mutex); + char *value = obj_is_trans_gps->valuestring; + + cfg.is_trans_gps = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "is_trans_gps", cJSON_CreateString("0")); + } + char *rootstr = cJSON_Print(root); + + ofstream ofs(cfg.config_path); + if (ofs.is_open()) + { + ofs.write(rootstr, strlen(rootstr)); + ofs.close(); + } + free(rootstr); + + cJSON_Delete(root); + + if (cfg.is_save_adsb & !ofs_adsb.is_open()) + { + lock_guard lg(cfg_mutex); + string adsb_path; + + time_t t = time(0); + char t_str[30]; + memset(t_str, 0, 30); + strftime(t_str, sizeof(t_str), "%Y_%m_%d_%H_%M_%S", localtime(&t)); + + adsb_path.append(cfg.dirpath); + adsb_path.append("/adsb").append(t_str).append(".txt"); + ofs_adsb.open(adsb_path); + } + else if (!cfg.is_save_adsb && ofs_adsb.is_open()) + { + + ofs_adsb.close(); + } + if (cfg.is_save_gps && !ofs_gps.is_open()) + { + lock_guard lg(cfg_mutex); + string gps_path; + + time_t t = time(0); + char t_str[30]; + memset(t_str, 0, 30); + strftime(t_str, sizeof(t_str), "%Y_%m_%d_%H_%M_%S", localtime(&t)); + + gps_path.append(cfg.dirpath); + gps_path.append("/gps").append(t_str).append(".txt"); + } + else if (!cfg.is_save_gps && ofs_gps.is_open()) + { + + ofs_gps.close(); + } + + if (cfg.is_save_yuanshi_iq && cfg.save_yuanshi_size_MByte > 0) + { + + lock_guard lg(cfg_mutex); + + if (cfg.need_new_file == true) + { + string yuanshi_path; + if (ofs_yuanshi_iq.is_open()) + { + ofs_yuanshi_iq.close(); + } + + time_t t = time(0); + char t_str[30]; + memset(t_str, 0, 30); + strftime(t_str, sizeof(t_str), "%Y_%m_%d_%H_%M_%S", localtime(&t)); + + yuanshi_path.append(cfg.dirpath); + yuanshi_path.append("/yuanshi").append(t_str).append(".dat"); + + yuanshi_iq_file_path_dq.push_back(yuanshi_path); + if (yuanshi_iq_file_path_dq.size() > cfg.max_save_file_count) + { + + string first = yuanshi_iq_file_path_dq.front(); + yuanshi_iq_file_path_dq.pop_front(); + + std::remove(first.c_str()); + } + + ofs_yuanshi_iq.open(yuanshi_path, ios::binary); + + cfg.need_new_file = false; + } + } + } + read_cfg_t0 = read_cfg_t1; + } + + sleep(5); + } +} +int32_t init9002(void **phandle) +{ + + // + { + memset(cfg.cwd, 0, sizeof(cfg.cwd)); + strcpy(cfg.rx_clock_ref, "external"); + memset(cfg.config_name, 0, sizeof(cfg.config_name)); + memset(cfg.config_path, 0, sizeof(cfg.config_path)); + cfg.rx_gain_val = 30.0; + cfg.is_save_adsb = 0; + cfg.is_save_gps = 0; + cfg.is_save_yuanshi_iq = 0; + cfg.save_yuanshi_size_MByte = 0; + cfg.cache_over_byte = 0; + cfg.need_new_file = true; + cfg.end_flag = 3; + } + Device_UHD *pthis = new Device_UHD(); + // getcwd(cwd, sizeof(cwd) - 1); + + strcpy(cfg.config_name, "lib9002config.json"); + int len = readlink("/proc/self/exe", cfg.cwd, sizeof(cfg.cwd) - 1); + cfg.cwd[len] = '\0'; + char *dir = dirname(cfg.cwd); + + strcpy(cfg.dirpath, dir); + sprintf(cfg.config_path, "%s/%s", dir, cfg.config_name); + // printf("config_path = %s\n", cfg.config_path); + + b_read_cfg_running = true; + + ifstream ifs(cfg.config_path); + string str; + + if (ifs.is_open()) + { + std::stringstream ss; + ss << ifs.rdbuf(); + str = ss.str(); + ifs.close(); + } + else + { + ofstream ofs(cfg.config_path); + string tmp_str = "{\"gain\":\"30.0\",\"clock_ref\":\"external\",\"is_save_adsb\":\"0\",\"new_file_zhouqi_s\":\"60\",\"max_save_file_count\":\"20\",\"is_save_yuanshi_iq\":\"0\",\"save_yuanshi_size_MByte\":\"0\",\"is_trans_gps\":\"0\",\"is_save_gps\":\"0\"}"; + + ofs.write(tmp_str.c_str(), tmp_str.size()); + str = tmp_str; + ofs.close(); + } + + stringstream ss; + + ss << "json_str = " << str << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + + pread_cfg_thread = new thread(read_cfg_func); + + *phandle = pthis; + return 0; +} +int32_t free9002(void **phandle) +{ + if (phandle != nullptr) + { + b_read_cfg_running = false; + if (pread_cfg_thread && pread_cfg_thread->joinable()) + { + pread_cfg_thread->join(); + delete pread_cfg_thread; + } + Device_UHD *pthis = (Device_UHD *)*phandle; + delete pthis; + pthis = nullptr; + phandle = nullptr; + + return 0; + } + return 0; +} +int32_t isDevOpened(void *phandle, int32_t *result) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *result = (int32_t)pthis->isDevOpened(); + return 0; + } + return -1; +} +int32_t findDevices(void *phandle, uhd::device_addrs_t *addrs) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *addrs = pthis->findDevices(); + return 0; + } + return -1; +} +int32_t openDevice(void *phandle, uhd::device_addr_t addr) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->openDevice(addr); + return 0; + } + return -1; +} +int32_t closeDevice(void *phandle) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->closeDevice(); + return 0; + } + return -1; +} +int32_t findDevInfos(void *phandle, char infos[1000]) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->findDevInfos(); + strcpy(infos, str.c_str()); + return 0; + } + return -1; +} +int32_t findDevInfos2(void *phandle, char infos[1000]) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->findDevInfos2(); + strcpy(infos, str.c_str()); + return 0; + } + return -1; +} +int32_t getDevTree(void *phandle, char tree_infos[1000]) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->getDevTree(); + strcpy(tree_infos, str.c_str()); + return 0; + } + return -1; +} +int32_t setDevClockSource(void *phandle, char sourceStr[1000]) +{ + stringstream ss; + ss << "sourceStr v:[" << sourceStr << "]" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + if (phandle) + { + + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevClockSource(sourceStr); + return 0; + } + return -1; +} +int32_t getDevClockSource(void *phandle, char sourceStr[1000]) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->getDevClockSource(); + strcpy(sourceStr, str.c_str()); + return 0; + } + return -1; +} +int32_t setDevInClock(void *phandle, double clockFreq) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevInClock(clockFreq); + return 0; + } + return -1; +} +int32_t getDevInClockRange(void *phandle, double *min, double *max) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->getDevInClockRange(); + *min = pthis->getDevParamMin(); + *max = pthis->getDevParamMax(); + return 0; + } + return -1; +} +int32_t getDevInClock(void *phandle, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevInClock(); + return 0; + } + return -1; +} +int32_t getDevRXChCount(void *phandle, size_t *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevRXChCount(); + return 0; + } + return -1; +} +int32_t getDevTXChCount(void *phandle, size_t *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevTXChCount(); + return 0; + } + return -1; +} +int32_t setDevParam(void *phandle, char chStr[50], UHD_ParamType paramType, double paramValue) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevParam(chStr, paramType, paramValue); + return 0; + } + return -1; +} +int32_t getDevParam(void *phandle, char chStr[50], UHD_ParamType paramType, double *paramValue) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *paramValue = pthis->getDevParam(chStr, paramType); + return 0; + } + return -1; +} +int32_t getDevParamMin(void *phandle, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevParamMin(); + return 0; + } + return -1; +} +int32_t getDevParamMax(void *phandle, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevParamMax(); + return 0; + } + return -1; +} +int32_t setAllAntenna(void *phandle, int32_t *result) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *result = pthis->setAllAntenna(); + return 0; + } + return -1; +} +int32_t setDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int jcqData) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevDDCParam_Any(chStr, jcqOffset, jcqData); + return 0; + } + return -1; +} +int32_t getDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int *jcqData) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + unsigned int t = 0; + pthis->getDevDDCParam_Any(chStr, jcqOffset, t); + *jcqData = t; + return 0; + } + return -1; +} +int32_t setDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int bSwitch) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevDataSwitch(chStr, dataType, ZDCh, bSwitch); + return 0; + } + return -1; +} +int32_t getDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int *bSwitch) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + bool b = false; + pthis->getDevDataSwitch(chStr, dataType, ZDCh, b); + *bSwitch = b; + return 0; + } + return -1; +} +int32_t setDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long centerfreq, int bw, int sample) +{ + + if (phandle) + { + + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevZDParam(chStr, ZDCh, centerfreq, bw, sample); + + return 0; + } + return -1; +} +int32_t getDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long *centerfreq, int *bw, int *sample) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + long long c = 0; + int b = 0; + int s = 0; + pthis->getDevZDParam(chStr, ZDCh, c, b, s); + + *centerfreq = c; + *bw = b; + *sample = s; + return 0; + } + return -1; +} +int32_t getDevZDCenterRange(void *phandle, char chStr[50], long long &min, long long &max) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->getDevZDCenterRange(chStr, min, max); + return 0; + } + return -1; +} +int32_t getDevZDBandWidthRange(void *phandle, char chStr[50], int &min, int &max) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->getDevZDBandWidthRange(chStr, min, max); + return 0; + } + return -1; +} +int32_t getDevZDSampleFreqRange(void *phandle, char chStr[50], int arr[13], int *valid_len) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + vector v = pthis->getDevZDSampleFreqRange(chStr); + *valid_len = v.size(); + for (int i = 0; i < v.size(); ++i) + { + arr[i] = v[i]; + } + return 0; + } + return -1; +} +int32_t setDevFFTParam(void *phandle, UHD_FFTWin fftWin, UHD_FFTLen fftLen, int fftPeriod_ms) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevFFTParam(fftWin, fftLen, fftPeriod_ms); + return 0; + } + return -1; +} +int32_t getDevFFTParam(void *phandle, UHD_FFTWin *fftWin, UHD_FFTLen *fftLen, int *fftPeriod_ms) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + UHD_FFTWin _fftWin; + UHD_FFTLen _fftLen; + int _fftPeriod_ms; + pthis->getDevFFTParam(_fftWin, _fftLen, _fftPeriod_ms); + *fftWin = _fftWin; + *fftLen = _fftLen; + *fftPeriod_ms = _fftPeriod_ms; + return 0; + } + return -1; +} +int32_t getDevRecvSpeed(void *phandle, int channel, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevSendSpeed(channel); + return 0; + } + return -1; +} +int32_t isDevRecving(void *phandle, int channel, int *result) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *result = pthis->isDevRecving(channel); + return 0; + } + return -1; +} + +class A +{ + public: + void callBackData_Func(int channel, void *pData, int iLength) { s_cbk(channel, pData, iLength); } +}; +A a; +int32_t startDevRecv(void *phandle, int channel, rx_data_cbk_t rx_cbk) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + + s_cbk = rx_cbk; + + pthis->callBackData = std::bind(&A::callBackData_Func, &a, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + pthis->startDevRecv(channel); + return 0; + } + return -1; +} +int32_t stopDevRecv(void *phandle, int channel) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->stopDevRecv(channel); + return 0; + } + return -1; +} +int32_t getDevSendSpeed(void *phandle, int channel, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevSendSpeed(channel); + return 0; + } + return -1; +} +int32_t isDevSending(void *phandle, int channel, int32_t *result) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *result = (int32_t)pthis->isDevSending(channel); + + return 0; + } + return -1; +} +int32_t startDevSend(void *phandle, int channel, unsigned long long send_cf, unsigned long long data_sf, char *filename, int bwhile) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->startDevSend(channel, send_cf, data_sf, filename, bwhile); + return 0; + } + return -1; +} +int32_t stopDevSend(void *phandle, int channel) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->stopDevSend(channel); + return 0; + } + return -1; +} +int32_t getGPSInfo(void *phandle, char gps_info[50]) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->getGPSInfo(); + + strcpy(gps_info, str.c_str()); + return 0; + } + return -1; +} +int32_t setLedEnable(void *phandle, int led_num, int bEnable) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setLedEnable(led_num, bEnable); + return 0; + } + return -1; +} +int32_t getLedEnable(void *phandle, int led_num, int *bEnable) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + bool b = false; + pthis->getLedEnable(led_num, b); + *bEnable = b; + return 0; + } + return -1; +} + +#include +#include +typedef struct context_t +{ + public: + void *pdev; + adsb_msg_cbk_t adsb_cbk; + thread *t_getgps; + + bool b_pool_over; + void *adsb_pusr; +} context_t; +unsigned long long msgNo = 0; +void *phandle_math_3a_decode = nullptr; +int lastframindex_ch0_zdddc = 0; +int lastframindex_ch0_fft = 0; +int lastframindex_ch1_zdddc = 0; +int lastframindex_ch1_fft = 0; +iq_cbk_t iq_cbk; +void *iq_pusr; +extern volatile bool iio_context_find_err[2]; + +#include +int BCDToDec(int bcd, int byt) +{ + if (1 == byt) + { + return bcd - (bcd >> 4) * 6; + } + else + { + int nDec = 0; + for (int i = 0; i < byt; i++) + { + uint8_t ucBcd = (uint8_t)(bcd >> (8 * i)); + nDec += ((ucBcd - (ucBcd >> 4) * 6) * (int)std::pow(100, i)); + } + return nDec; + } +} + +time_t get_2000_1_1_000_time_t() +{ + struct tm tt; + tt.tm_year = 2000 - 1900; + tt.tm_mon = 0; + tt.tm_mday = 1; + tt.tm_hour = 0; + tt.tm_min = 0; + tt.tm_sec = 0; + return mktime(&tt); +} + +int flag1 = 0; +int is_leap_year(int year) +{ + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); +} +void day_of_year_to_month_day(int year, int day_of_year, int *month, int *day) +{ + int days_per_month[] = { + 31, // 1 月 大 + 28, // 2 月 平 + 31, // 3 月 大 + 30, // 4 月 小 + 31, // 5 月 大 + 30, // 6 月 小 + 31, // 7 月 大 + 31, // 8 月 大 + 30, // 9 月 小 + 31, // 10月 大 + 30, // 11月 小 + 31 // 12月 大 + }; + if (is_leap_year(year)) + { + days_per_month[1] = 29; + } + *month = 1; + *day = day_of_year; + for (int i = 0; i < 12 && *day > days_per_month[i]; ++i) + { + *day -= days_per_month[i]; + (*month)++; + } +} + +// uint64_t rev_cbk_lenght_per_s = 0; +// time_t recv_time_t0 = time(0); + +int last_type = 0; + +time_t print_t1; +time_t print_t2; + +string get_UHD_Enum_DataType(UHD_Enum_DataType v) +{ + string ttttttt; + switch (v) + { + + case zd_ddc: { + ttttttt = "zd_ddc"; + } + break; + case kd_ddc: { + ttttttt = "kd_ddc"; + } + break; + case kd_fft: { + ttttttt = "kd_fft"; + } + break; + case GPS_Info: { + ttttttt = "GPS_Info"; + } + break; + default: + break; + } + return ttttttt; +} +void _rx_data_cbk_t(int channel, void *pdata, int length) +{ + + if (iq_cbk != NULL && channel != 0) + { + iq_cbk(iq_pusr, pdata, length); + } + // if (phandle_math_3a_decode) + // { + // + // int nErr = SpecDecdInput(phandle_math_3a_decode, ((char *)pdata + 64), 2048, 0); + // int a = 0; + // } + + UHD_Struct_KDDC *p = (UHD_Struct_KDDC *)pdata; + if (last_type != (int)p->DataType) + { + last_type = (int)p->DataType; + } + + // 丢帧打印t + { + + int data_channel = p->Channel; + + if (data_channel == 0 && p->DataType == UHD_Enum_DataType::zd_ddc) + { + int cha = p->FrameIndex - lastframindex_ch0_zdddc; + time_t tmp_t = time(0); + if (p->FrameIndex != 0 && lastframindex_ch0_zdddc != 0 && cha != 1 && cha != -65535 && difftime(tmp_t, print_t1) > 5) + { + stringstream ss; + ss << "loseframe!!!=> channel:[" << channel << "]\tcurrent_index:[" << p->FrameIndex << "]\tlast_index:[" << lastframindex_ch0_zdddc << "]" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + print_t1 = tmp_t; + } + lastframindex_ch0_zdddc = p->FrameIndex; + } + // TODO:test else if (channel == 1 && p->DataType == UHD_Enum_DataType::zd_ddc) + else if (data_channel == 1 && p->DataType == UHD_Enum_DataType::zd_ddc) + { + int cha = p->FrameIndex - lastframindex_ch1_zdddc; + time_t tmp_t = time(0); + if (p->FrameIndex != 0 && lastframindex_ch1_zdddc != 0 && cha != 1 && cha != -65535 && difftime(tmp_t, print_t2) > 5) + { + stringstream ss; + ss << "loseframe!!!=> channel:[" << channel << "]\tcurrent_index:[" << p->FrameIndex << "]\tlast_index:[" << lastframindex_ch1_zdddc << "]" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + print_t2 = tmp_t; + } + lastframindex_ch1_zdddc = p->FrameIndex; + } + } + + // 原始文件存储 + + if (p->DataType == zd_ddc && channel == 0) + { + { + lock_guard lg(cfg_mutex); + cfg.cache_over_byte += length; + int mbyte = cfg.cache_over_byte / (1024 * 1024); + + if (cfg.is_save_yuanshi_iq && cfg.save_yuanshi_size_MByte > 0 && ofs_yuanshi_iq.is_open() && (mbyte < (cfg.save_yuanshi_size_MByte))) + { + ofs_yuanshi_iq.write((char *)pdata, length); + return; + } + else if (cfg.save_yuanshi_size_MByte >= mbyte) + { + + cfg.cache_over_byte = 0; + cfg.is_save_yuanshi_iq = 0; + cfg.need_new_file = true; + } + } + + struct_DataTime *dt = &p->DateTm; + struct tm tt2; + memset(&tt2, 0, sizeof(struct tm)); + uint64_t result_nsec = 0; + + switch (dt->TimeType) + { + case Enum_TimeType_Invlid: { + result_nsec = 0; + } + break; + case Enum_TimeType_IRIG_B: { + int B_Year = BCDToDec(dt->TimeInfo.IRIGB_Head.Year, 1) + 2000; + int B_Day = BCDToDec(dt->TimeInfo.IRIGB_Head.Day, 2); + int B_Hour = BCDToDec(dt->TimeInfo.IRIGB_Head.Hour, 1); + int B_Minute = BCDToDec(dt->TimeInfo.IRIGB_Head.Minute, 1); + int B_Second = BCDToDec(dt->TimeInfo.IRIGB_Head.Second, 1); + uint32_t B_NSec = dt->TimeInfo.IRIGB_Head.NanoSec; + + tm my_tm; + int month; + int day; + day_of_year_to_month_day(B_Year, B_Day, &month, &day); + + tt2.tm_year = B_Year - 1900; + tt2.tm_mon = month - 1; + tt2.tm_mday = day; + tt2.tm_hour = B_Hour; + tt2.tm_min = B_Minute; + tt2.tm_sec = B_Second; + + // stringstream ss; + // ss << " y1=" << tt2.tm_year << "\n" + // << " M1=" << tt2.tm_mon << "\n" + // << " D1=" << tt2.tm_mday << "\n" + // << " H1=" << tt2.tm_hour << "\n" + // << " m1=" << tt2.tm_min << "\n" + // << " s1=" << tt2.tm_sec << "\n" + // << " nsec" << B_NSec << endl; + // error_callback_call(ss.str().c_str()); + + time_t t2 = mktime(&tt2); + // t2+= dt->TimeInfo.RMC_Head.NanoSec; + result_nsec = t2 * 1000000000 + B_NSec; + } + break; + case Enum_TimeType_RMC: { + + // time_t t1 = get_2000_1_1_000_time_t(); + + tt2.tm_year = dt->TimeInfo.RMC_Head.Year + 2000 - 1900; + tt2.tm_mon = dt->TimeInfo.RMC_Head.Month - 1; + tt2.tm_mday = dt->TimeInfo.RMC_Head.Day; + tt2.tm_hour = dt->TimeInfo.RMC_Head.Hour; + tt2.tm_min = dt->TimeInfo.RMC_Head.Minute; + tt2.tm_sec = dt->TimeInfo.RMC_Head.Second; + + time_t t2 = mktime(&tt2); + // t2+= dt->TimeInfo.RMC_Head.NanoSec; + result_nsec = t2 * 1000000000 + dt->TimeInfo.RMC_Head.NanoSec + (8ull * 3600 * 1000000000ull); + + if (flag1 == 0) + { + // stringstream ss; + // ss << "\n dt_type" << (int)dt->TimeType << "\n" + // << " y1=" << tt2.tm_year<< "\n" + // << " M1=" << tt2.tm_mon<< "\n" + // << " D1=" << tt2.tm_mday<< "\n" + // << " H1=" << tt2.tm_hour<< "\n" + // << " m1=" << tt2.tm_min<< "\n" + // << " s1=" << tt2.tm_sec << "\n" + // << " nsec" << tt2.tm_sec << "\n" + // << "result_nsec" << result_nsec << "\n" + // << "this is for input" << endl; + // error_callback_call(ss.str().c_str()); + flag1 = 1; + } + } + break; + case Enum_TimeType_ABSCNTTM: { + uint64_t sec_n = ((uint64_t)dt->TimeInfo.ABSCNTTM_Head.Second) * 1000000000; + result_nsec = dt->TimeInfo.ABSCNTTM_Head.NanoSec + sec_n; + } + break; + case Enum_TimeType_REFCNTTM: { + uint64_t sec_n = ((uint64_t)dt->TimeInfo.REFCNTTM_Head.Second) * 1000000000; + result_nsec = dt->TimeInfo.REFCNTTM_Head.NanoSec + sec_n; + } + break; + default: + break; + } + + if (result_nsec > 0 && phandle_math_3a_decode) + { + + int nErr = SpecDecdInput(phandle_math_3a_decode, ((char *)pdata + 64), 2048, result_nsec); + if (nErr != 0) + { + stringstream ss; + ss << "t数据溢出" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + } + } + } +} + +int flag2 = 0; + +// // // TODO:以下三行测试用,没有关闭操作,记得及时删除 +// #include +// fstream g_fsTst2("test_time.txt",ios::app); +// fstream g_fsTst1("test_for_server.txt",ios::app); +time_t mesc_to_time_t(int64_t mesc) +{ + auto sec = std::chrono::duration_cast(std::chrono::milliseconds(mesc)); + auto time_point = std::chrono::system_clock::time_point(sec); + return std::chrono::system_clock::to_time_t(time_point); +} +void SpecDecdRetClbkFun(void *hdl, int taskId, MSpecDecdSigType sigType, char *data, int length) +{ + context_t *pthis = (context_t *)hdl; + USpecDecdRet *pRet = (USpecDecdRet *)data; + char cRet[1024] = {0}; + char cRet2[1024] = {0}; + switch (sigType) + { + case MSpecDecdSigType::ADSB: { + time_t u64Time = pRet->ADSBRet.Time / 1000000000; + // time_t l_tm_t = u64Time + 8 * 3600; + time_t l_tm_t = u64Time; // 改为UTC时间基准 + + tm TmTime = *(gmtime(&l_tm_t)); + TmTime.tm_year += 1900; + TmTime.tm_mon += 1; + TmTime.tm_hour; + + // // TODO:测试用 + // { + // unsigned long long ullSec = ((TmTime.tm_hour * 60) + TmTime.tm_min) * 60 + TmTime.tm_sec; + // unsigned long long ullNSec = pRet->ADSBRet.Time % 1000000000; + // unsigned long long ullTime = ((ullSec & 0x3FFFF)) << 30 | (ullNSec & 0x3FFFFFFF); + // char cTime[6] = {0}; + // memcpy(cTime, &ullTime, 6); + // for (int i = 0, j = 5; i < j; i++, j--) //小端转大端 + // { + // char cTemp = cTime[i]; + // cTime[i] = cTime[j]; + // cTime[j] = cTemp; + // } + // std::string strMsg = ""; + // for (int i = 0; i < 6; i++) + // { + // char cByte[16] = {0}; + // sprintf(cByte, " %02X", cTime[i] & 0xFF); + // strMsg += cByte; + // } + // //测试用 + // char cMsg[256] = {0}; + // sprintf(cMsg, "%s\n", strMsg.c_str()); + // // g_fsTst2.write(cMsg, strlen(cMsg)); + // // g_fsTst2.flush(); + // } + + if (flag2 == 0) + { + // stringstream ss; + // ss << " y2=" << TmTime.tm_year << "\n" + // << " M2=" << TmTime.tm_mon << "\n" + // << " D2=" << TmTime.tm_mday << "\n" + // << " H2=" << TmTime.tm_hour << "\n" + // << " m2=" << TmTime.tm_min << "\n" + // << " s2=" << TmTime.tm_sec << "\n" + // << " SpecDecdRetClbkFun " << "\n" + // << " TmTime.tm_hour" << TmTime.tm_hour << " pRet->ADSBRet.Time" << pRet->ADSBRet.Time << " u64Time" << u64Time << std::endl; + // error_callback_call(ss.str().c_str()); + flag2 = 1; + } + int nMsgLen = 0; + for (int i = sizeof(pRet->ADSBRet.Msg) - 1; i >= 0; i--) + { + if (0 != pRet->ADSBRet.Msg[i]) + { + nMsgLen = i + 1; + break; + } + } + if (nMsgLen >= 28) + { + nMsgLen = 28; + } + else if (nMsgLen >= 14) + { + nMsgLen = 14; + } + else + { + nMsgLen = 4; + } + // char cMsg[1024] = {0}; + // for (int i = 0; i < nMsgLen; i++) + // { + // sprintf(&cMsg[i*2], "%02x", pRet->ADSBRet.Msg[i]); + // } + // sprintf(cRet, "#%03d 0x%06x %08s %02d Lon:%f Lat:%f Alt:%f %s, %04d-%02d-%02d %02d:%02d:%02d\r", + // ++msgNo, pRet->ADSBRet.ICAO, pRet->ADSBRet.FltNo, pRet->ADSBRet.Label, pRet->ADSBRet.Longitude, pRet->ADSBRet.Latitude, pRet->ADSBRet.Altitude, cMsg, + // TmTime.tm_year, TmTime.tm_mon, TmTime.tm_mday, TmTime.tm_hour, TmTime.tm_min, TmTime.tm_sec); + // 按规定格式输出报文 + std::string strMsg = "1A"; + switch (nMsgLen / 2) + { + case 2: + strMsg += " 31"; + break; + case 7: + strMsg += " 32"; + break; + case 14: + strMsg += " 33"; + break; + default: + break; + } + unsigned long long ullSec = ((TmTime.tm_hour * 60) + TmTime.tm_min) * 60 + TmTime.tm_sec; + // if (ullSec > 86400) //86400 = 24*60*60 + // { + // return; + // } + unsigned long long ullNSec = pRet->ADSBRet.Time % 1000000000; + unsigned long long ullTime = ((ullSec & 0x3FFFF) << 30) | (ullNSec & 0x3FFFFFFF); + char cTime[6] = {0}; + memcpy(cTime, &ullTime, 6); + for (int i = 0, j = 5; i < j; i++, j--) // 小端转大端 + { + char cTemp = cTime[i]; + cTime[i] = cTime[j]; + cTime[j] = cTemp; + } + for (int i = 0; i < 6; i++) + { + if (26 == cTime[i]) + { + strMsg += " 1A"; + } + char cByte[16] = {0}; + sprintf(cByte, " %02X", cTime[i] & 0xFF); + strMsg += cByte; + } + int nRSSI = (int)(pRet->ADSBRet.RSSI + 0.5); + if (26 == (nRSSI & 0xFF)) + { + strMsg += " 1A"; + } + char cRSSI[16] = {0}; + sprintf(cRSSI, " %02X", nRSSI & 0xFF); + strMsg += cRSSI; + for (int i = 0; i < nMsgLen / 2; i++) + { + if ('1' == pRet->ADSBRet.Msg[2 * i] && ('a' == pRet->ADSBRet.Msg[2 * i + 1] || 'A' == pRet->ADSBRet.Msg[2 * i + 1])) + { + strMsg += " 1A"; + } + char cByte[8] = {0}; + sprintf(cByte, " %C%C", toupper(pRet->ADSBRet.Msg[2 * i]), toupper(pRet->ADSBRet.Msg[2 * i + 1])); + strMsg += cByte; + } + sprintf(cRet, "%s", strMsg.c_str()); + } + break; + default: + break; + } + // if (strlen(cRet) > 0) + // { + // // printf(cRet); + // // printf("\n"); + // } + + // //TODO:测试用 + // { + // char cMsg[256] = {0}; + // sprintf(cMsg, "%s\r", cRet); + // g_fsTst1.write(cMsg, strlen(cMsg)); + // g_fsTst1.flush(); + // } + + if (cfg.is_save_adsb && ofs_adsb.is_open()) + { + ofs_adsb << cRet; + } + pthis->adsb_cbk(pthis->adsb_pusr, cRet, strlen(cRet)); +} + +int32_t ex_declare init_adsb9002(void **phandle, adsb_msg_cbk_t cbk, void *pusr) +{ + context_t *pthis = new context_t(); + pthis->adsb_cbk = cbk; + pthis->adsb_pusr = pusr; + phandle_math_3a_decode = 0; + pthis->pdev = nullptr; + init9002(&pthis->pdev); + if (!pthis->pdev) + { + + stringstream ss; + ss << "pdev init error" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + return -1; + } + uhd::device_addrs_t s; + + if (findDevices(pthis->pdev, &s) != 0) + { + + stringstream ss; + ss << "findDevices error!" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + return -1; + } + else + { + } + if (openDevice(pthis->pdev, s[0]) != 0) + { + + stringstream ss; + ss << "openDevice error!" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + + return -1; + } + else + { + } + // 关闭开关 + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::ZDDCSwitch, 0, false); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::KFFTSwitch, 0, false); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::KDDCSwitch, 0, false); + + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::ZDDCSwitch, 8, false); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KFFTSwitch, 8, false); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KDDCSwitch, 8, false); + // 启动接收线程 + if (startDevRecv(pthis->pdev, 0, _rx_data_cbk_t) != 0) + { + stringstream ss; + ss << "startDevRecv 0 error!" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + return -1; + } + else + { + } + if (startDevRecv(pthis->pdev, 1, _rx_data_cbk_t) != 0) + { + stringstream ss; + ss << "startDevRecv 1 error!" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + return -1; + } + else + { + } + // 设置默认参数 + setDevParam(pthis->pdev, "RX0", UHD_ParamType::CenterFreq, 1089000000); + setDevParam(pthis->pdev, "RX0", UHD_ParamType::BandWidth, 6000000); + setDevParam(pthis->pdev, "RX0", UHD_ParamType::SampleFreq, 24000000); + setDevParam(pthis->pdev, "RX0", UHD_ParamType::Gain, cfg.rx_gain_val); + + setDevParam(pthis->pdev, "RX1", UHD_ParamType::CenterFreq, 15000000); + setDevParam(pthis->pdev, "RX1", UHD_ParamType::BandWidth, 30000000); + setDevParam(pthis->pdev, "RX1", UHD_ParamType::SampleFreq, 61440000); + setDevParam(pthis->pdev, "RX1", UHD_ParamType::Gain, cfg.rx_gain_val); + + setDevZDParam(pthis->pdev, "RX0", 0, 1090000000, 3000000, 12000000); + setDevClockSource(pthis->pdev, cfg.rx_clock_ref); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::ZDDCSwitch, 0, true); + + int nFs = 12000000; + SpecDecdInit(&phandle_math_3a_decode, 0, MSpecDecdSigType::ADSB, MSpecDecdDaType::ShortC, nFs, 1, SpecDecdRetClbkFun, pthis); + + pthis->b_pool_over = false; + pthis->t_getgps = new thread( + [=](context_t *ppthis) { + time_t t1 = time(0); + int print_times = 0; + while (ppthis->b_pool_over == false) + { + time_t t2 = time(0); + if (difftime(t2, t1) > 2) + { + t1 = t2; + char gpsinfo[50]; + memset(gpsinfo, 0, 50); + + getGPSInfo(ppthis->pdev, gpsinfo); + string gpsinfostr = gpsinfo; + if (gpsinfostr.size() <= 0) + { + continue; + } + + int mposi = gpsinfostr.find('M'); + + if (mposi != std::string::npos || mposi < 10) + { + + string sub; + double h = stod(gpsinfostr.substr(0, mposi)); + char zl = gpsinfostr.substr(mposi + 1, 1).at(0); + if (zl != '1') + { + continue; + } + char ew = gpsinfostr.substr(mposi + 2, 1).at(0); + int lon_du = stoi(gpsinfostr.substr(mposi + 3, 3)); + int lon_fen_zhengshu = stoi(gpsinfostr.substr(mposi + 6, 2)); + int lon_fen_xiaoshu = stoi(gpsinfostr.substr(mposi + 8, 4)); + + char ns = gpsinfostr.substr(mposi + 12, 1).at(0); + int lat_du = stoi(gpsinfostr.substr(mposi + 13, 2)); + int lat_fen_zhengshu = stoi(gpsinfostr.substr(mposi + 15, 2)); + int lat_fen_xiaoshu = stoi(gpsinfostr.substr(mposi + 17, 4)); + + double lon_fen = lon_fen_zhengshu + lon_fen_xiaoshu / 10000.0; + double longitude = lon_du + lon_fen / 60.0; + + // 根据东西经度调整符号 + if (ew == 'W') + { + longitude = -longitude; + } + + // 计算纬度 + // 将分数的小数部分转换为小数形式(假设小数部分是4位数字) + double lat_fen = lat_fen_zhengshu + lat_fen_xiaoshu / 10000.0; + double latitude = lat_du + lat_fen / 60.0; + + // 根据南北纬度调整符号 + if (ns == 'S') + { + latitude = -latitude; + } + + if (print_times < 3) + { + print_times++; + stringstream ss; + ss << "经度:[" << longitude << "°]\t纬度:[" << latitude << "°]\t\t海拔:[" << h << "m]" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + } + + HULC_Status_Message message; + + message.set_latitude(latitude); + message.set_longitude(longitude); + message.Alt = (int16_t)h; + + auto lat = message.get_latitude(); + auto lon = message.get_longitude(); + auto alt = message.Alt; + + auto bin = mem2hex(packet_to_escape_format(message.toBinary())); + + char *c = (char *)bin.c_str(); + int len = bin.size(); + + if (cfg.is_trans_gps == 1) + { + pthis->adsb_cbk(pthis->adsb_pusr, c, len); + } + if (cfg.is_save_gps && ofs_gps.is_open()) + { + ofs_gps << "经度:" << longitude << "°" << "\t纬度: " << latitude << "°" << "\t海拔: " << h << "m" << "\tbin_str:" << bin.c_str() << endl; + } + } + } + usleep(500000); + } + }, + pthis); + int a = 1; + *phandle = pthis; + if (iio_context_find_err[0] || iio_context_find_err[1]) + { + + stringstream ss; + ss << "9002 init failed!" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + return -1; + } + return 0; +} + +int32_t ex_declare free_adsb9002(void **phandle) +{ + context_t *pthis = (context_t *)*phandle; + if (pthis->pdev) + { + int32_t result = 0; + isDevOpened(pthis->pdev, &result); + if (result > 0) + { + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::ZDDCSwitch, 0, 0); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::KFFTSwitch, 0, 0); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::KDDCSwitch, 0, 0); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::ZDDCSwitch, 8, 0); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KFFTSwitch, 8, 0); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KDDCSwitch, 8, 0); + if (stopDevRecv(pthis->pdev, 0) != 0) + { + + stringstream ss; + ss << "stopDevRecv 0 error!" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + } + else + { + } + if (stopDevRecv(pthis->pdev, 1) != 0) + { + + stringstream ss; + ss << "stopDevRecv 1 error!" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + } + else + { + } + closeDevice(pthis->pdev); + if (pthis->t_getgps->joinable()) + { + pthis->b_pool_over = true; + pthis->t_getgps->join(); + } + pthis->t_getgps = nullptr; + } + free9002(&pthis->pdev); + } + pthis->pdev = nullptr; + SpecDecdFree(&phandle_math_3a_decode); + if (ofs_adsb.is_open()) + { + ofs_adsb.close(); + } + if (ofs_gps.is_open()) + { + ofs_gps.close(); + } + if (ofs_yuanshi_iq.is_open()) + { + ofs_yuanshi_iq.close(); + } + return 0; +} + +int ex_declare set_iq_cbk(void *phandle, iq_cbk_t recv_cbk, void *pusr) +{ + if (phandle) + { + context_t *pthis = (context_t *)phandle; + iq_cbk = recv_cbk; + iq_pusr = pusr; + return 0; + } + return -1; +} + +int ex_declare set_less_30Mhz_ddc_param(void *phandle, uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw) +{ + if (phandle) + { + context_t *pthis = (context_t *)phandle; + // setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::ZDDCSwitch, 0, false); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::ZDDCSwitch, 8, false); + setDevZDParam(pthis->pdev, "RX1", 8, cf_hz, bw_hz, sf_hz); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::ZDDCSwitch, 8, true); + return 0; + } + return -1; +} + +int ex_declare set_less_30Mhz_fft_param(void *phandle, int fftWin, int fftLen, int fftPeriod_ms, int8_t sw) +{ + if (phandle) + { + context_t *pthis = (context_t *)phandle; + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KFFTSwitch, 8, false); + setDevFFTParam(pthis->pdev, (UHD_FFTWin)fftWin, (UHD_FFTLen)fftLen, fftPeriod_ms); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KFFTSwitch, 8, true); + + return 0; + } + return -1; +} + +#pragma region 王永琛代码 +struct Global_Read_Buffer +{ + Global_Read_Buffer(); + ~Global_Read_Buffer(); + std::string read() + { + std::lock_guard g(mtx); + std::string ret; + std::swap(ret, buffer); + return ret; + } + void append(std::string buf) + { + std::lock_guard g(mtx); + buffer.append(buf); + } + + void *phandle{}; + std::mutex mtx; + std::string buffer; +}; + +#include "error_callback.h" +void my_error_cbk_t(const char *err_msg) +{ + cout << err_msg << endl; +} + +Global_Read_Buffer t; + +void adsb2(void *pusr, char *pmsg, int msglen) +{ + t.append(std::string(pmsg, msglen)); +} +void _iq_cbk_t(void *phandle, void *pdata, uint64_t length) +{ + // cout << "FUNC:[dev_uhd_c::" << __func__ << "]\tcbk called!!!!!!!" << endl; +} +Global_Read_Buffer::Global_Read_Buffer() +{ + init_adsb9002(&phandle, adsb2, nullptr); + set_iq_cbk(phandle, _iq_cbk_t, nullptr); +} + +Global_Read_Buffer::~Global_Read_Buffer() +{ + free_adsb9002(&phandle); +} + +std::string adsb_read() +{ + // return t.read(); + return t.read(); +} + +#include +void adsb_read(char *buf, std::uint16_t *len) +{ + auto s = t.read(); + *len = s.size(); + std::memcpy(buf, s.c_str(), *len); +} + +int adsb_set_iq_cbk(iq_cbk_t recv_cbk, void *pusr) +{ + stringstream ss; + ss << "adsb_set_iq_cbk " << pusr << " " << recv_cbk << " " << std::endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + return set_iq_cbk(t.phandle, recv_cbk, pusr); +} + +int adsb_set_less_30Mhz_ddc_param(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw) +{ + + stringstream ss; + ss << "adsb_set_less_30Mhz_ddc_param " << cf_hz << " " << sf_hz << " " << bw_hz << " " << sw << " " << std::endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + return set_less_30Mhz_ddc_param(t.phandle, cf_hz, sf_hz, bw_hz, sw); +} + +int adsb_set_less_30Mhz_fft_param(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw) +{ + stringstream ss; + ss << "adsb_set_less_30Mhz_fft_param " << fftWin << " " << fftLen << " " << fftPeriod_ms << " " << sw << std::endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + return set_less_30Mhz_fft_param(t.phandle, fftWin, fftLen, fftPeriod_ms, sw); +} + +#pragma endregion \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/device_uhd_c.h b/module/dll_source_lzy/114/out/lib/device_uhd_c.h new file mode 100644 index 0000000..8f4f347 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/device_uhd_c.h @@ -0,0 +1,108 @@ +#ifndef _DEVICE_UHD_C_H_ +#define _DEVICE_UHD_C_H_ + +#include "device_uhd_struct.h" +#include "makelib_common.h" +#include "error_callback.h" +// #include "device_uhd_cpp.h" +#include + +typedef void (*rx_data_cbk_t)(int channel, void *pdata, int length); + +extern "C" +{ + int32_t ex_declare init9002(void **phandle); + int32_t ex_declare free9002(void **phandle); + int32_t ex_declare isDevOpened(void *phandle, int32_t *result); + int32_t ex_declare findDevices(void *phandle, uhd::device_addrs_t *addrs); + int32_t ex_declare openDevice(void *phandle, uhd::device_addr_t addr); + int32_t ex_declare closeDevice(void *phandle); + int32_t ex_declare findDevInfos(void *phandle, char infos[1000]); + int32_t ex_declare findDevInfos2(void *phandle, char infos[1000]); + int32_t ex_declare getDevTree(void *phandle, char tree_infos[1000]); + int32_t ex_declare setDevClockSource(void *phandle, char sourceStr[1000]); + int32_t ex_declare getDevClockSource(void *phandle, char sourceStr[1000]); + int32_t ex_declare setDevInClock(void *phandle, double clockFreq); + int32_t ex_declare getDevInClockRange(void *phandle, double *min, double *max); + int32_t ex_declare getDevInClock(void *phandle, double *val); + int32_t ex_declare getDevRXChCount(void *phandle, size_t *val); + int32_t ex_declare getDevTXChCount(void *phandle, size_t *val); + int32_t ex_declare setDevParam(void *phandle, char chStr[50], UHD_ParamType paramType, double paramValue); + int32_t ex_declare getDevParam(void *phandle, char chStr[50], UHD_ParamType paramType, double *paramValue); + int32_t ex_declare getDevParamMin(void *phandle, double *val); + int32_t ex_declare getDevParamMax(void *phandle, double *val); + int32_t ex_declare setAllAntenna(void *phandle, int32_t *result); + int32_t ex_declare setDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int jcqData); + int32_t ex_declare getDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int *jcqData); + int32_t ex_declare setDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int bSwitch); + int32_t ex_declare getDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int *bSwitch); + int32_t ex_declare setDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long centerfreq, int bw, int sample); + int32_t ex_declare getDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long *centerfreq, int *bw, int *sample); + int32_t ex_declare getDevZDCenterRange(void *phandle, char chStr[50], long long &min, long long &max); + int32_t ex_declare getDevZDBandWidthRange(void *phandle, char chStr[50], int &min, int &max); + int32_t ex_declare getDevZDSampleFreqRange(void *phandle, char chStr[50], int arr[13], int *valid_len); + int32_t ex_declare setDevFFTParam(void *phandle, UHD_FFTWin fftWin, UHD_FFTLen fftLen, int fftPeriod_ms); + int32_t ex_declare getDevFFTParam(void *phandle, UHD_FFTWin *fftWin, UHD_FFTLen *fftLen, int *fftPeriod_ms); + int32_t ex_declare getDevRecvSpeed(void *phandle, int channel, double *val); + int32_t ex_declare isDevRecving(void *phandle, int channel, int32_t *result); + int32_t ex_declare startDevRecv(void *phandle, int channel, rx_data_cbk_t rx_cbk); + int32_t ex_declare stopDevRecv(void *phandle, int channel); + int32_t ex_declare getDevSendSpeed(void *phandle, int channel, double *val); + int32_t ex_declare isDevSending(void *phandle, int channel, int32_t *rusult); + int32_t ex_declare startDevSend(void *phandle, int channel, unsigned long long send_cf, unsigned long long data_sf, char *filename, int bwhile); + int32_t ex_declare stopDevSend(void *phandle, int channel); + int32_t ex_declare getGPSInfo(void *phandle, char gps_info[50]); + int32_t ex_declare setLedEnable(void *phandle, int led_num, int bEnable); + int32_t ex_declare getLedEnable(void *phandle, int led_num, int *bEnable); + + typedef void (*adsb_msg_cbk_t)(void *pusr, char *pmsg, int msglen); + + int32_t ex_declare init_adsb9002(void **phandle, adsb_msg_cbk_t cbk, void *pusr); + int32_t ex_declare free_adsb9002(void **phandle); + + // 新接口 :时间 20251107 , + // TODO: 1 增加 dma 回调函数 iq 的(ddc,ffc) + + // iq 数据回调 + + typedef void (*iq_cbk_t)(void *phandle, void *pdata, uint64_t length); + int ex_declare set_iq_cbk(void *phandle, iq_cbk_t recv_cbk, void *pusr); + + int ex_declare set_less_30Mhz_ddc_param(void *phandle, uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + int ex_declare set_less_30Mhz_fft_param(void *phandle, int fftWin, int fftLen, int fftPeriod_ms, int8_t sw); + + + + +} + +#include "device_uhd_c.h" +#include +#include + +#define MY_LIBRARY_EXPORTS +#ifdef _WIN32 // Windows 平台 +#ifdef MY_LIBRARY_EXPORTS // 如果正在构建库 +#define ex_declare __declspec(dllexport) // 导出符号 +#else +#define ex_declare __declspec(dllimport) // 导入符号 +#endif +#else // 对于 Linux 和其他 Unix-like 系统 +#define ex_declare __attribute__((visibility("default"))) // 导出符号 +#endif +#include +extern "C" +{ + ex_declare void adsb_read(char *buf, std::uint16_t *len); + // IQ 数据接口 + typedef void (*iq_cbk_t)(void *phandle, void *pdata, uint64_t length); + typedef int (*adsb_set_iq_cbk_t)(iq_cbk_t recv_cbk, void *pusr); + typedef int (*adsb_set_less_30Mhz_ddc_param_t)(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + typedef int (*adsb_set_less_30Mhz_fft_param_t)(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw); + ex_declare int adsb_set_iq_cbk(iq_cbk_t recv_cbk, void *pusr); + ex_declare int adsb_set_less_30Mhz_ddc_param(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + ex_declare int adsb_set_less_30Mhz_fft_param(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw); + +} + +#endif //_DEVICE_UHD_C_H_ \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/device_uhd_cpp.cpp b/module/dll_source_lzy/114/out/lib/device_uhd_cpp.cpp new file mode 100644 index 0000000..0b903f4 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/device_uhd_cpp.cpp @@ -0,0 +1,2615 @@ +#include "device_uhd_cpp.h" +// #include "UHD_Probe.h" +#include "DDCSampTable.h" +#include "FIRGenLowpass_no_ipp.h" +#include "device_uhd_c.h" +#include "device_uhd_struct.h" +#include +#include +#include +#include +#include +#include +using namespace std; +using namespace uhd; + +#define addr_dev_ref_time 0x4200 // "参考时钟" b:0101 GPS ; b:1010 外参考 ; b:1001 内参考 +#define addr_dev_version 0x420c // "设备版本" +#define addr_rx_data_sw 0x4210 //"数据接收开关" +#define addr_dev_led 0x421c +#define addr_rx_kd_fft_sw 0x4220 // fft 开关 +#define addr_rx_kd_fft_len 0x4224 // fft 长度 1k,2k,4k,8k,16k,32k +#define addr_rx_kd_fft_type 0x4228 // fft 类型 0jxc,1hnc,2hmc,3blackman +#define addr_rx_kd_fft_preiod 0x422c // fft 周期 1-1000 ms +#define addr_rx_kd_ddc_data_sw 0x4240 // 宽带数据开关 +#define addr_rx_kd_ddc_ch 0x4264 // 宽带通道号 +#define addr_rx_kd_ddc_fc 0x4270 //"过滤系数" +#define addr_rx_kd_ddc_ch_sw 0x4208 //"中频开关" 切换通道 接收模块 1:9002模块 0:30M模块 +#define addr_rx_kd_kz_speed 0x4214 // 快照速度 +#define addr_rx_ch0_30Mhz_cw 0x4278 // 接收30Mhz 控制字 +#define addr_rx_ch1_30MHz_cw 0x427c // 接收30Mhz 控制字 +#define addr_rx_kd_ch0_ddc_sf 0x4244 //"采样率" +#define addr_rx_kd_ch0_ddc_bw 0x4248 //"带宽" +#define addr_rx_kd_ch0_ddc_cf_h16 0x424c //"中心频率 高16" +#define addr_rx_kd_ch0_ddc_cf_l32 0x4250 //"中心频率 低32" +#define addr_rx_kd_ch0_ddc_df 0x4268 //"采样因子/抽取率因子" +#define addr_rx_kd_ch0_kz_sw 0x4230 // 快照开关 +#define addr_rx_kd_ch0_kz_pack_size 0x4234 // 快照包大小 +#define addr_rx_kd_ch1_ddc_sf 0x4254 //"采样率" +#define addr_rx_kd_ch1_ddc_bw 0x4258 //"带宽" +#define addr_rx_kd_ch1_ddc_cf_h16 0x425c //"中心频率 高16" +#define addr_rx_kd_ch1_ddc_cf_l32 0x4260 //"中心频率 低32" +#define addr_rx_kd_ch1_ddc_df 0x426c //"采样因子/抽取率因子" +#define addr_rx_kd_ch1_kz_sw 0x4238 // 快照开关 +#define addr_rx_kd_ch1_kz_pack_size 0x423c // 快照包大小 +#define addr_rx_zd_ch 0x4280 // 窄带通道号 +#define addr_rx_zd_ddc_data_sw 0x4284 // 窄带数据开关 +#define addr_rx_zd_df 0x4288 // 窄带抽取倍数 +#define addr_rx_zd_bw 0x428c // 窄带带宽 +#define addr_rx_zd_cw 0x4290 // 窄带控制字 +#define addr_rx_zd_fc 0x4294 // 窄带过滤系数 +#define addr_rx_zd_sf 0x4298 // 窄带采样率 +#define addr_tx_send_data_cw 0x42c0 // 发送总开关 +#define addr_tx_send_ch_sw 0x42c4 // 发送通道选择 +#define addr_tx_ch0_insert_rate 0x42c8 // 插值率 +#define addr_tx_ch1_insert_rate 0x42cc // 插值率 +#define addr_tx_ch0_30MHz_cw 0x42d0 // 发送30Mhz 控制字 +#define addr_tx_ch1_30MHz_cw 0x42d4 // 发送30Mhz 控制字 +#define addr_tm_set_h 0x42e0 // 计时高位 +#define addr_tm_set_l 0x42e4 // 计时低位 +//----------------------------------------------------------------------------------------- + +#define my_iio_rx_block_point_size_big (2112 * 512) // #define my_iio_block_point_size (2112*4) +#define my_iio_block_point_size_little (2112 * 512) +#define my_iio_tx_block_point_size (2112 * 512) +#define ach_sf_count_max_hz (15.36 * 1000 * 1000) +#define rx_kz_speed_hz (1.62 * 1000 * 1000) +// #define rx_net_speed_max (500*1024*1024) +#define ZDExtraMin 0 // 窄带抽取倍数的序号,最小 +#define ZDExtraMax 12 // 窄带抽取倍数的序号,最大 +#define kz_sum ((double)(491520000 / 2)) +#define GHz (1000 * 1000 * 1000.0) +#define MHz (1000 * 1000.0) + +static unsigned long long kd_sf_all = 0; +static unsigned long long sf_all = 0; +static unsigned long long tmp_block_size[2] = {0, 0}; +static unsigned long long tmp_sf_all = 0; +volatile bool iio_context_find_err[2] = {false}; //[0]find_device [1]find_channel +typedef struct ThreadRecvParam +{ + int channel; + void *pThis; +} ThreadRecvParam; + +std::string get_Addr_string(int addr) +{ + string tmp; + switch (addr) + { + case addr_dev_ref_time: { + tmp = "参考时钟"; + } + break; + case addr_dev_version: { + tmp = "设备版本"; + } + break; + case addr_rx_data_sw: { + tmp = "rx开关"; + } + break; + case addr_dev_led: { + tmp = "led灯"; + } + break; + case addr_rx_kd_fft_sw: { + tmp = "rx_kd_fft开关"; + } + break; + case addr_rx_kd_fft_len: { + tmp = "rx_kd_fft长度"; + } + break; + case addr_rx_kd_fft_type: { + tmp = "rx_kd_fft类型"; + } + break; + case addr_rx_kd_fft_preiod: { + tmp = "rx_kd_fft周期"; + } + break; + case addr_rx_kd_ddc_data_sw: { + tmp = "rx_kd数据开关"; + } + break; + case addr_rx_kd_ddc_ch: { + tmp = "rx_kd通道号"; + } + break; + case addr_rx_kd_ddc_fc: { + tmp = "rx_kd_过滤系数"; + } + break; + case addr_rx_kd_ddc_ch_sw: { + tmp = "rx_kd_中频切换通道开关"; + } + break; + case addr_rx_kd_kz_speed: { + tmp = "rx_kd_快照速度"; + } + break; + case addr_rx_ch0_30Mhz_cw: { + tmp = "kd_ch0接收30Mhz控制字"; + } + break; + case addr_rx_ch1_30MHz_cw: { + tmp = "kd_ch1接收30Mhz控制字"; + } + break; + case addr_rx_kd_ch0_ddc_sf: { + tmp = "kd_ch0采样率"; + } + break; + case addr_rx_kd_ch0_ddc_bw: { + tmp = "kd_ch0带宽"; + } + break; + case addr_rx_kd_ch0_ddc_cf_h16: { + tmp = "kd_ch0中心频率高16"; + } + break; + case addr_rx_kd_ch0_ddc_cf_l32: { + tmp = "kd_ch0中心频率低32"; + } + break; + case addr_rx_kd_ch0_ddc_df: { + tmp = "kd_ch0采样抽取率因子"; + } + break; + case addr_rx_kd_ch0_kz_sw: { + tmp = "kd_ch0快照开关"; + } + break; + case addr_rx_kd_ch0_kz_pack_size: { + tmp = "kd_ch0快照包大小"; + } + break; + case addr_rx_kd_ch1_ddc_sf: { + tmp = "kd_ch1采样率"; + } + break; + case addr_rx_kd_ch1_ddc_bw: { + tmp = "kd_ch1带宽"; + } + break; + case addr_rx_kd_ch1_ddc_cf_h16: { + tmp = "kd_ch1中心频率高16"; + } + break; + case addr_rx_kd_ch1_ddc_cf_l32: { + tmp = "kd_ch1中心频率低32"; + } + break; + case addr_rx_kd_ch1_ddc_df: { + tmp = "kd_ch1采样抽取率因子"; + } + break; + case addr_rx_kd_ch1_kz_sw: { + tmp = "kd_ch1快照开关"; + } + break; + case addr_rx_kd_ch1_kz_pack_size: { + tmp = "kd_ch1快照包大小"; + } + break; + case addr_rx_zd_ch: { + tmp = "zd_通道号"; + } + break; + case addr_rx_zd_ddc_data_sw: { + tmp = "zd_数据开关"; + } + break; + case addr_rx_zd_df: { + tmp = "zd_抽取倍数"; + } + break; + case addr_rx_zd_bw: { + tmp = "zd_带宽"; + } + break; + case addr_rx_zd_cw: { + tmp = "zd_控制字"; + } + break; + case addr_rx_zd_fc: { + tmp = "zd_过滤系数"; + } + break; + case addr_rx_zd_sf: { + tmp = "zd_采样率"; + } + break; + case addr_tx_send_data_cw: { + tmp = "tx总开关"; + } + break; + case addr_tx_send_ch_sw: { + tmp = "tx通道选择"; + } + break; + case addr_tx_ch0_insert_rate: { + tmp = "tx0插值率"; + } + break; + case addr_tx_ch1_insert_rate: { + tmp = "tx1插值率"; + } + break; + case addr_tx_ch0_30MHz_cw: { + tmp = "tx030Mhz控制字"; + } + break; + case addr_tx_ch1_30MHz_cw: { + tmp = "tx130Mhz控制字"; + } + break; + case addr_tm_set_h: { + tmp = "计时高位"; + } + break; + case addr_tm_set_l: { + tmp = "计时低位"; + } + break; + default: + tmp = "未知类型"; + break; + } + return tmp; +} +std::string get_UHD_ParamType(UHD_ParamType v) +{ + + string tmp; + switch (v) + { + case CenterFreq: { + tmp = "CenterFreq"; + } + break; + case BandWidth: { + tmp = "BandWidth"; + } + break; + case SampleFreq: { + tmp = "SampleFreq"; + } + break; + case Gain: { + tmp = "Gain"; + } + break; + case PowerRef: { + tmp = "PowerRef"; + } + break; + case KDDCSwitch: { + tmp = "KDDCSwitch"; + } + break; + case KFFTSwitch: { + tmp = "KFFTSwitch"; + } + break; + case ZDDCSwitch: { + tmp = "ZDDCSwitch"; + } + break; + case ZDDCParam: { + tmp = "ZDDCParam"; + } + break; + case KFFTParam: { + tmp = "KFFTParam"; + } + break; + case ClockSource: { + tmp = "ClockSource"; + } + break; + case SnapshotSwitch: { + tmp = "SnapshotSwitch"; + } + break; + case Snapshot_tm_s: { + tmp = "Snapshot_tm_s"; + } + break; + + default: + break; + } + return tmp; +}; + +void addr_val_mix(int64_t addr, int64_t val, std::string &sstr) +{ + sstr.clear(); + char str[50]; + for (int i = 0; i < 50; ++i) + { + str[i] = 0; + } + sprintf(str, "%08x%08x", (unsigned int)addr, (unsigned int)val); + sstr = str; +} +bool set_esun_rx_addr_val(iio_context *ctx, int64_t addr, int64_t val) +{ + + if (addr == addr_rx_zd_fc || addr == addr_rx_kd_ddc_fc) + { + } + else + { + // stringstream ss1; + // ss1 << "detail:[" << get_Addr_string(addr) << "]寄存器\taddr:[0x" << std::hex << addr << "]\tval:[" << std::dec << val << "]" << endl; + // MACROS_ERROR_CBK_CALL(ss1.str().c_str()); + } + if (ctx) + { + struct iio_device *phy; + struct iio_channel *chan; + phy = iio_context_find_device(ctx, "adrv9002-phy"); + if (phy == NULL) + { + if (!iio_context_find_err[0]) + { + iio_context_find_err[0] = true; + stringstream ss; + ss << "set_esun_rx_addr_val iio_context_find_device error" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + } + return false; + } + chan = iio_device_find_channel(phy, "voltage0", false); + if (chan == NULL) + { + if (!iio_context_find_err[1]) + { + iio_context_find_err[1] = true; + stringstream ss; + ss << "set_esun_rx_addr_val iio_device_find_channel error" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + } + return false; + } + + std::string str; + addr_val_mix(addr, val, str); + char vl[20]; + for (int i = 0; i < 20; ++i) + { + vl[i] = 0; + } + strcpy(vl, str.c_str()); + iio_channel_attr_write(chan, "esun_reg", vl); + return true; + } + stringstream ss; + ss << "ctx is nullptr error" << endl; + return false; +} +bool get_esun_rx_addr_val(iio_context *ctx, int64_t addr, int64_t &val) +{ + if (ctx) + { + struct iio_device *phy; + struct iio_channel *chan; + phy = iio_context_find_device(ctx, "adrv9002-phy"); + if (phy == NULL) + { + if (!iio_context_find_err[0]) + { + iio_context_find_err[0] = true; + stringstream ss; + ss << "get_esun_rx_addr_val iio_context_find_device error" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + } + return false; + } + chan = iio_device_find_channel(phy, "voltage0", false); + if (chan == NULL) + { + if (!iio_context_find_err[1]) + { + iio_context_find_err[1] = true; + stringstream ss; + ss << "get_esun_rx_addr_val iio_device_find_channel error" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + } + return false; + } + char str[20]; + memset(str, 0, 20); + sprintf(str, "%x", addr); + iio_channel_attr_write(chan, "esun_reg", str); + iio_channel_attr_read(chan, "esun_reg", str, 20); + val = atoi(str); + return true; + } + return false; +} +static int stream_channels_get_enable(const struct iio_device *dev, struct iio_channel **chan, bool tx) +{ + int c; + const char *const channels[] = {"voltage0_i", "voltage0_q", "voltage0", "voltage1"}; + for (c = 0; c < 2; c++) + { + const char *str = channels[tx * 2 + c]; + chan[c] = iio_device_find_channel(dev, str, tx); + if (!chan[c]) + { + return -ENODEV; + } + if (!iio_channel_is_enabled(chan[c])) + { + iio_channel_enable(chan[c]); + } + } + return 0; +} +static int stream_channels_get_disenable(const struct iio_device *dev, struct iio_channel **chan, bool tx) +{ + int c; + const char *const channels[] = {"voltage0_i", "voltage0_q", "voltage0", "voltage1"}; + for (c = 0; c < 2; c++) + { + const char *str = channels[tx * 2 + c]; + chan[c] = iio_device_find_channel(dev, str, tx); + if (!chan[c]) + { + return -ENODEV; + } + if (iio_channel_is_enabled(chan[c])) + { + iio_channel_disable(chan[c]); + } + } + return 0; +} +uhd::device_addrs_t Device_UHD::findDevices() +{ + device_addrs_t dev_list; + dev_list.clear(); + device_addr_t t; + t.uri = "ip:172.22.0.2"; + this->bFindedDevice = true; + dev_list.push_back(t); + p_usrp.ctx_isopen = false; + return dev_list; +} +bool Device_UHD::openDevice(uhd::device_addr_t addr) +{ + bool bRet = false; + if (this->bFindedDevice) + { + try + { + this->devAddr = addr; + uhd::usrp::multi_usrp::sptr tmp_URSP; + tmp_URSP.ctx = iio_create_context_from_uri("ip:172.22.0.2"); + // tmp_URSP.ctx = iio_create_context_from_uri("ip:192.168.1.141"); + if (tmp_URSP.ctx != NULL) + { + p_usrp = tmp_URSP; + p_usrp.ctx_isopen = true; + bRet = true; + spThread_Recv[0] = NULL; + spThread_Recv[1] = NULL; + // memset(&rxKDParam, 0, sizeof(rxKDParam)); + for (int i = 0; i < 2; i++) + { + rxKDParam[i].KCH = i; + rxKDParam[i].CenterFreq = 0; + rxKDParam[i].BandWidth = 0; /*getDevParam("RX" + i, UHD_ParamType::BandWidth);*/ + rxKDParam[i].SampleFreq = 0; /* getDevParam("RX" + i, UHD_ParamType::SampleFreq);*/ + for (int y = 0; y < ZDChMax; y++) + { + rxKDParam[i].ZDParam[y].KCH = i; + rxKDParam[i].ZDParam[y].ZCH = i * ZDChMax + y; + } + rxKDParam[i].gain = 0; + rxKDParam[i].Snapshot_Switch = false; + rxKDParam[i].Snapshot_tm_s = 0; + txKDParam[i].gain = -40; + led_val = 0; + } + kd_centerfreq_sw = 0; + fftWin = UHD_FFTWin::blkmc; + fftLen = UHD_FFTLen::_32768; + fftPeriod = 10; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + p_usrp.ctx_isopen = false; + bRet = false; + } + } + return bRet; +} +void Device_UHD::closeDevice() +{ + if (p_usrp.ctx != NULL) + { + stopDevRecv(0); + stopDevRecv(1); + stopDevSend(0); + stopDevSend(1); + // 释放前,需把所有引用都赋值NULL,方可释放 + // pTree = NULL; // + iio_context_destroy(p_usrp.ctx); + p_usrp.ctx = NULL; // 内部会自动释放指针 + p_usrp.ctx_isopen = false; + } +} +bool Device_UHD::isDevOpened() const +{ + bool ret = p_usrp.ctx_isopen; + return ret; +} +string GetUHDTree(const uhd::fs_path &path, uhd::property_tree::sptr tree) +{ + return "GetUHDTree\r\n"; +} +string Device_UHD::getDevTree() +{ + return "getDevTree\r\n"; +} +string Device_UHD::findDevInfos() +{ + string ret; + char tmp[200]; + if (p_usrp.ctx != NULL) + { + try + { + { // 查询设备信息 + ret += "ip:172.22.0.2\r\n"; + } + { // 查看设备数量、名称、时钟源 + snprintf(tmp, 200, "get_num_mboards:%d", 0); + ret += string(tmp) + "\r\n"; + ret += "\r\n"; + } + { + } + { // 查询接收通道参数 + // int num = (int)pUSRP->get_rx_num_channels(); + snprintf(tmp, 200, "get_rx_num_channels:%d", 2); + ret += string(tmp) + "\r\n"; + for (int i = 0; i < 2; i++) + { + } + ret += "\r\n"; + } + { // 查询发射通道参数 + snprintf(tmp, 200, "get_tx_num_channels:%d", 2); + ret += string(tmp) + "\r\n"; + for (int i = 0; i < 2; i++) + { + { // 查询发射频率范围 + snprintf(tmp, 200, "get_tx_freq_range(%d):%f~%f", i, 0, 6 * GHz); + ret += string(tmp) + "\r\n"; + } + } + ret += "\r\n"; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return "findDevInfos\r\n"; +} +string Device_UHD::findDevInfos2() +{ + return "ok"; +} +size_t Device_UHD::getDevRXChCount() +{ + size_t bRet = 0; + try + { + if (p_usrp.ctx != NULL) + { + bRet = 2; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return bRet; +} +size_t Device_UHD::getDevTXChCount() +{ + size_t bRet = 0; + try + { + if (p_usrp.ctx != NULL) + { + bRet = 2; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return bRet; +} + +bool Device_UHD::setDevClockSource(string sourceStr) +{ + + bool ret = true; + try + { + if (p_usrp.ctx != NULL) + { + if (sourceStr.compare("gpsdo") == 0) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_dev_ref_time, 5); + } + else if (sourceStr.compare("external") == 0) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_dev_ref_time, 10); + } + else if (sourceStr.compare("internal") == 0) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_dev_ref_time, 9); + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + ret = false; + } + return ret; +} +string Device_UHD::getDevClockSource() +{ + string ret = "internal"; + try + { + if (p_usrp.ctx != NULL) + { + int64_t val; + get_esun_rx_addr_val(p_usrp.ctx, (int64_t)addr_dev_ref_time, val); + if (val == 5) + { + ret = string("gpsdo"); + } + else if (val == 9) + { + ret = string("internal"); + } + else if (val == 10) + { + ret = string("external"); + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return ret; +} +bool Device_UHD::setDevInClock(double clockFreq) +{ + bool ret = true; + return ret; +} +void Device_UHD::getDevInClockRange() +{ + try + { + if (p_usrp.ctx != NULL) + { + fParamMin = 10000000; + fParamMax = 10000000; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } +} +double Device_UHD::getDevInClock() +{ + double ret = 0; + try + { + if (p_usrp.ctx != NULL) + { + ret = 10000000; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return ret; +} +bool Device_UHD::setDevParam(string chStr, UHD_ParamType paramType, double paramValue) +{ + + stringstream ss; + ss << "ch_str:[" << chStr << "]\tType:[" << get_UHD_ParamType(paramType) << "]\tValue[" << paramValue << "]" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + bool ret = true; + try + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, 0); + if (p_usrp.ctx != NULL) + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + bool bTX = chStr.find("TX") != string::npos; + bool tmp_zd_switch = 0; + + //"频点", "带宽", "采样率", "增益", "校准电平" + + if (!bTX) + { + for (int i = 0; i < 8; ++i) + { + tmp_zd_switch += rxKDParam[channel].ZDParam[i].bSwitch_ZDDC; + } + switch (paramType) + { + case UHD_ParamType::CenterFreq: + // if (tmp_zd_switch) + // { + // return false; + // } + if (!bTX) + { + rxKDParam[channel].old_cf = rxKDParam[channel].CenterFreq; + rxKDParam[channel].CenterFreq = paramValue; + if (rxKDParam[channel].old_cf == 0) + { + rxKDParam[channel].old_cf = rxKDParam[channel].CenterFreq; + } + } + break; + case UHD_ParamType::BandWidth: + // if (tmp_zd_switch) + // { + // return false; + // } + if (!bTX) + { + rxKDParam[channel].old_bw = rxKDParam[channel].BandWidth; + rxKDParam[channel].BandWidth = paramValue; + if (rxKDParam[channel].old_bw == 0) + { + rxKDParam[channel].old_bw = rxKDParam[channel].BandWidth; + } + } + break; + case UHD_ParamType::SampleFreq: + // if (tmp_zd_switch) + // { + // return false; + // } + if (!bTX) + { + if (rxKDParam[channel].bSwitch_KDDC == true) + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + // unsigned long long pre_cnt = sfcnt - rxKDParam[channel].SampleFreq + paramValue; + // if (pre_cnt > ach_sf_count_max_hz + 1) + // { + // return false; + // } + // sf_all = pre_cnt; + } + rxKDParam[channel].old_sf = rxKDParam[channel].SampleFreq; + rxKDParam[channel].SampleFreq = paramValue; + if (rxKDParam[channel].old_sf == 0) + { + rxKDParam[channel].old_sf = rxKDParam[channel].SampleFreq; + } + } + break; + case UHD_ParamType::Gain: + if (!bTX) + { + + struct iio_device *phy; + struct iio_channel *chn = nullptr; + phy = iio_context_find_device(this->p_usrp.ctx, "adrv9002-phy"); + if (channel == 0) + { + chn = iio_device_find_channel(phy, "voltage0", false); + } + else if (channel == 1) + { + chn = iio_device_find_channel(phy, "voltage1", false); + } + int retno = iio_channel_attr_write_double(chn, "hardwaregain", paramValue); + if (retno != 0) + { + ret = false; + } + else + { + ret = true; + rxKDParam[channel].gain = paramValue; + } + } + return false; + break; + case UHD_ParamType::PowerRef: + break; + case UHD_ParamType::SnapshotSwitch: + if (!bTX) + { + bool bSwitch = (bool)paramValue; + if (bSwitch == true && rxKDParam[channel].Snapshot_Switch == false) + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + // unsigned long long pre_cnt = sfcnt + rx_kz_speed_hz; + // if (pre_cnt > ach_sf_count_max_hz + 1) + // { + // return false; + // } + // sf_all = pre_cnt; + } + rxKDParam[channel].Snapshot_Switch = (bool)paramValue; + // 设置获取本来的宽带 通道开关 并置为0; + unsigned char uSwitch = 0; + if (bSwitch == true) + { + rxKDParam[channel].bSwitch_KDDC = false; + for (int i = 0; i < 2; i++) + { + if (rxKDParam[i].bSwitch_KDDC) + { + uSwitch |= 0x01 << i; + } + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_data_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_data_sw, uSwitch); + } + set_esun_rx_addr_val(p_usrp.ctx, channel == 0 ? addr_rx_kd_ch0_kz_sw : addr_rx_kd_ch1_kz_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, channel == 0 ? addr_rx_kd_ch0_kz_sw : addr_rx_kd_ch1_kz_sw, rxKDParam[channel].Snapshot_Switch); + return true; + } + return false; + break; + case UHD_ParamType::Snapshot_tm_s: { + if (!bTX) + { + if (rxKDParam[channel].SampleFreq < 0) + { + return false; + } + int tmp_t_max = (int)(kz_sum / rxKDParam[channel].SampleFreq); + if (paramValue > tmp_t_max || paramValue < 0) + { + // paramValue = tmp_t_max; + rxKDParam[channel].Snapshot_tm_s = tmp_t_max; + } + else + { + rxKDParam[channel].Snapshot_tm_s = paramValue; + } + } + } + break; + defualt: + break; + } + + int kd_cur_indx = channel; + double kddcsamp = rxKDParam[channel].SampleFreq; + int ExtraPer = 0; + unsigned long long CenterFreq = rxKDParam[channel].CenterFreq; + long long BandWidth = rxKDParam[channel].BandWidth; + unsigned long long kd_centerfreq_h16 = CenterFreq & 0xffff00000000; + unsigned long long kd_centerfreq_l32 = CenterFreq & 0xffffffff; + int t = rxKDParam[channel].Snapshot_tm_s; + unsigned long long pack_size = ((unsigned int)((t * rxKDParam[channel].SampleFreq * 32.0) / (2048 * 8.0) / 16)) * 16; + set_esun_rx_addr_val(p_usrp.ctx, channel == 0 ? addr_rx_kd_ch0_kz_pack_size : addr_rx_kd_ch1_kz_pack_size, pack_size); + bool enable_6G_ch = 0; + if (CenterFreq >= 30 * MHz && CenterFreq < 6 * GHz) + { + enable_6G_ch = 1; + } + else if (CenterFreq < 30 * MHz && CenterFreq >= 0) + { + enable_6G_ch = 0; + } + if (channel == 0) + { + if (enable_6G_ch == 1) + kd_centerfreq_sw |= 0x01; + if (enable_6G_ch == 0) + kd_centerfreq_sw &= 0x02; + } + if (channel == 1) + { + if (enable_6G_ch == 1) + kd_centerfreq_sw |= 0x02; + if (enable_6G_ch == 0) + kd_centerfreq_sw &= 0x01; + } + // bool bSwitch = false; + // int kd_ddc_up = 0; + int kd_FilterCoef[128] = {0}; + if (channel == 0) + { + for (int i = KDDCSampIndxMin0; i <= KDDCSampIndxMax0; i++) + { + if (KDDCSampTab0[i][0] == kddcsamp) + { + ExtraPer = i; + break; + } + } + } + else if (channel == 1) + { + for (int i = KDDCSampIndxMin1; i <= KDDCSampIndxMax1; i++) + { + if (KDDCSampTab1[i][0] == kddcsamp) + { + ExtraPer = i; + break; + } + } + } + + if (CenterFreq < 30 * MHz) + { + int kd_30M_ctrword = (int)((double)(CenterFreq - 15000000.0) / define_KDDCSampleMax1 * pow(2, 32)); + if (kd_cur_indx == 0) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_ch0_30Mhz_cw, kd_30M_ctrword); + } + if (kd_cur_indx == 1) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_ch1_30MHz_cw, kd_30M_ctrword); + } + } + int kd_filterLen = SetFIRLpCoef2(kd_FilterCoef, 128, (double)BandWidth / kddcsamp, 18, 1.0); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_ch_sw, kd_centerfreq_sw); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_ch, kd_cur_indx); + + // if(rxKDParam[channel].old_bw != BandWidth || rxKDParam[channel].old_sf != kddcsamp) + // { + // if (kd_filterLen > 0) + // { + // for (int i = 0; i < kd_filterLen; i++) + // { + // set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_fc, kd_FilterCoef[i]); + // } + // } + // } + if (!(rxKDParam[channel].old_cf != CenterFreq && rxKDParam[channel].old_bw == BandWidth && rxKDParam[channel].old_sf == kddcsamp)) + { + if (kd_filterLen > 0) + { + + stringstream ss1; + ss1 << "设置kd滤波器系数" << endl; + MACROS_ERROR_CBK_CALL(ss1.str().c_str()); + for (int i = 0; i < kd_filterLen; i++) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_fc, kd_FilterCoef[i]); + } + } + } + if (kd_cur_indx == 0) + { + struct iio_channel *chn = nullptr; + struct iio_device *phy; + phy = iio_context_find_device(p_usrp.ctx, "adrv9002-phy"); + chn = iio_device_find_channel(phy, "altvoltage0", true); + bool b1 = (CenterFreq >= (30 * MHz)); + bool b2 = (CenterFreq < (6 * GHz)); + if (b1 && b2) + { + iio_channel_attr_write_longlong(chn, "RX1_LO_frequency", CenterFreq); + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_df, ExtraPer); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_bw, BandWidth); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_sf, kddcsamp); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_cf_h16, kd_centerfreq_h16); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_cf_l32, kd_centerfreq_l32); + } + else if (kd_cur_indx == 1) + { + struct iio_channel *chn = nullptr; + struct iio_device *phy; + phy = iio_context_find_device(p_usrp.ctx, "adrv9002-phy"); + chn = iio_device_find_channel(phy, "altvoltage1", true); + if (CenterFreq >= 30 * MHz && CenterFreq < 6 * GHz) + { + iio_channel_attr_write_longlong(chn, "RX2_LO_frequency", CenterFreq); + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_df, ExtraPer); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_bw, BandWidth); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_sf, kddcsamp); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_cf_h16, kd_centerfreq_h16); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_cf_l32, kd_centerfreq_l32); + } + + struct iio_device *phy; + struct iio_channel *chn = nullptr; + phy = iio_context_find_device(this->p_usrp.ctx, "adrv9002-phy"); + if (channel == 0) + { + chn = iio_device_find_channel(phy, "voltage0", false); + } + else if (channel == 1) + { + chn = iio_device_find_channel(phy, "voltage1", false); + } + // int retno = iio_channel_attr_write_double(chn, "hardwaregain", this->rxKDParam[channel].gain); + // if (retno!=0) + // { + // ret = false; + // } + // else{ + // ret = true; + // rxKDParam[channel].gain = paramValue; + // } + } + + if (bTX) + { + if (paramType == UHD_ParamType::Gain) + { + + struct iio_device *phy; + struct iio_channel *chn = nullptr; + phy = iio_context_find_device(this->p_usrp.ctx, "adrv9002-phy"); + if (channel == 0) + { + chn = iio_device_find_channel(phy, "voltage0", true); + } + else if (channel == 1) + { + chn = iio_device_find_channel(phy, "voltage1", true); + } + int retno = iio_channel_attr_write_double(chn, "hardwaregain", paramValue); + if (retno != 0) + { + ret = false; + } + else + { + ret = true; + txKDParam[channel].gain = paramValue; + } + } + } + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, 3); + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + ret = false; + } + return ret; +} +double Device_UHD::getDevParam(string chStr, UHD_ParamType paramType) +{ + double ret; + try + { + if (p_usrp.ctx != NULL) + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + bool bTX = chStr.find("TX") != string::npos; + //"频点", "带宽", "采样率", "增益", "校准电平" + if (!bTX) + { + switch (paramType) + { + case UHD_ParamType::CenterFreq: + // ret = bTX ? p_usrp->get_tx_freq(channel) : pUSRP->get_rx_freq(channel); + if (!bTX) + { + ret = rxKDParam[channel].CenterFreq; //= ret; + } + break; + case UHD_ParamType::BandWidth: + if (!bTX) + { + ret = rxKDParam[channel].BandWidth; //= ret; + } + break; + case UHD_ParamType::SampleFreq: + if (!bTX) + { + ret = rxKDParam[channel].SampleFreq; // = ret; + } + break; + case UHD_ParamType::Gain: + ret = rxKDParam[channel].gain; + break; + case UHD_ParamType::PowerRef: + ret = 10000000; + break; + case UHD_ParamType::SnapshotSwitch: + ret = rxKDParam[channel].Snapshot_Switch; + break; + case UHD_ParamType::Snapshot_tm_s: + ret = rxKDParam[channel].Snapshot_tm_s; + } + } + if (bTX) + { + if (paramType == UHD_ParamType::Gain) + { + ret = txKDParam[channel].gain; + } + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return ret; +} +bool Device_UHD::setAllAntenna() +{ + bool ret = true; + return ret; +} +void Device_UHD::getDevParamRange(string chStr, UHD_ParamType paramType, double ¶mMin, double ¶mMax) +{ + try + { + if (p_usrp.ctx != NULL) + { + // uhd::meta_range_t range; + int channel = chStr.find("0") != string::npos ? 0 : 1; + bool bTX = chStr.find("TX") != string::npos; + //"频点", "带宽", "采样率", "增益", "校准电平" + switch (paramType) + { + case UHD_ParamType::CenterFreq: + fParamMin = 0; + fParamMax = 6 * GHz; + break; + case UHD_ParamType::BandWidth: + fParamMin = 0; + fParamMax = 40000000; + break; + case UHD_ParamType::SampleFreq: + fParamMin = 0; + fParamMax = define_KDDCSampleMax1; + break; + case UHD_ParamType::Gain: + if (!bTX) + { + fParamMin = 0; + fParamMax = 34; + } + break; + case UHD_ParamType::PowerRef: + fParamMin = 0; + fParamMax = 0; + break; + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } +} +double Device_UHD::getDevParamMin() const +{ + return fParamMin; +} +double Device_UHD::getDevParamMax() const +{ + return fParamMax; +} +bool Device_UHD::setDevDDCParam_Any(string chStr, unsigned int jcqOffset, unsigned int jcqData) +{ + bool ret = true; + if (p_usrp.ctx != NULL) + { + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + set_esun_rx_addr_val(p_usrp.ctx, jcqOffset, jcqData); + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::getDevDDCParam_Any(string chStr, unsigned int jcqOffset, unsigned int &jcqData) +{ + bool ret = true; + if (p_usrp.ctx != NULL) + { + int64_t va; + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + get_esun_rx_addr_val(p_usrp.ctx, jcqOffset, va); + jcqData = va; + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::setDevDataSwitch(string chStr, UHD_ParamType dataType, unsigned char ZDCh, bool bSwitch) +{ + stringstream ss; + ss << "ch_str:[" << chStr << "]\tType:[" << get_UHD_ParamType(dataType) << "]\tzdch:[" << (int)ZDCh << "]\tsw:[" << bSwitch << "]" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + + int xx = bSwitch == true ? 1 : -1; + bool ret = true; + if (p_usrp.ctx != NULL) + { + try + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, 0); + int channel = chStr.find("0") != string::npos ? 0 : 1; + switch (dataType) + { + case KDDCSwitch: { + if (bSwitch != rxKDParam[channel].bSwitch_KDDC) + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + // unsigned long long pre_cnt = sfcnt + (xx * rxKDParam[channel].SampleFreq); + // if (pre_cnt > ach_sf_count_max_hz + 1) + // { + // return false; + // } + // sf_all = pre_cnt; + } + // close zk + if (bSwitch == true) + { + rxKDParam[channel].Snapshot_Switch = false; + // set_esun_rx_addr_val(p_usrp.ctx, channel == 0 ? addr_rx_kd_ch0_kz_sw : addr_rx_kd_ch1_kz_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, channel == 0 ? addr_rx_kd_ch0_kz_sw : addr_rx_kd_ch1_kz_sw, false); + // rx_kd_ddc_sw |= (1 << channel); + } + else + { + + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ch, ZDCh); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ddc_data_sw, 0); + // rx_kd_ddc_sw &= ~(1 << channel); + } + // get old kdsw + unsigned char uSwitch = 0; + for (int i = 0; i < 2; i++) + { + if (rxKDParam[i].bSwitch_KDDC) + { + uSwitch |= (0x01 << i); + } + } + // set kdsw + if (bSwitch) + { + uSwitch |= (1 << channel); + } + else + { + uSwitch &= ~(1 << channel); + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_data_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_data_sw, uSwitch); + rxKDParam[channel].bSwitch_KDDC = bSwitch; + } + break; + case KFFTSwitch: { + if (bSwitch != rxKDParam[channel].bSwitch_KFFT) + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + double t_fftlen = (double)fftLen; + sfcnt += rxKDParam[j].bSwitch_KFFT * (t_fftlen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + // unsigned long long pre_cnt = sfcnt + xx * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + // if (pre_cnt > ach_sf_count_max_hz + 1) + // { + // return false; + // } + // sf_all = pre_cnt; + } + unsigned char uSwitch = 0; + for (int i = 0; i < 2; i++) + { + if (rxKDParam[i].bSwitch_KFFT) + { + uSwitch |= 0x01 << i; + } + } + if (bSwitch) + { + uSwitch |= (1 << channel); + } + else + { + uSwitch &= ~(1 << channel); + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_sw, uSwitch); + rxKDParam[channel].bSwitch_KFFT = bSwitch; + } + break; + case ZDDCSwitch: { + if (bSwitch != rxKDParam[channel].ZDParam[ZDCh % ZDChMax].bSwitch_ZDDC) + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + // unsigned long long pre_cnt = sfcnt + xx * rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq; + // if (pre_cnt > ach_sf_count_max_hz + 1) + // { + // return false; + // } + // sf_all = pre_cnt; + } + if (ZDCh >= 0 && ZDCh <= ZDChMax * 2) + { + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].bSwitch_ZDDC = bSwitch; + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ch, ZDCh); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ddc_data_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ch, ZDCh); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ddc_data_sw, bSwitch); + } + else + ret = false; + } + break; + default: + ret = false; + break; + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, 3); + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::getDevDataSwitch(string chStr, UHD_ParamType dataType, unsigned char ZDCh, bool &bSwitch) +{ + bool ret = true; + if (p_usrp.ctx != NULL) + { + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + switch (dataType) + { + case KDDCSwitch: { + bSwitch = rxKDParam[channel].bSwitch_KDDC; + } + break; + case KFFTSwitch: { + bSwitch = rxKDParam[channel].bSwitch_KFFT; + } + break; + case ZDDCSwitch: { + if (ZDCh >= 0 && ZDCh <= ZDChMax * 2) + { + bSwitch = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].bSwitch_ZDDC; + } + } + break; + default: + break; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::setDevFFTParam(UHD_FFTWin fftWin, UHD_FFTLen fftLen, int fftPeriod_ms) +{ + stringstream ss; + ss << "fftWin:[" << get_UHD_FFTWin(fftWin) << "]\tUHD_FFTLen:[" << (int)fftLen << "]\tfftPeriod_ms:[" << (int)fftPeriod_ms << "]" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + double curfftlen = (double)fftLen; + bool ret = false; + if (p_usrp.ctx != NULL) + { + try + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, 0); + { + unsigned long long d_fft_old = 0; + unsigned long long d_fft_new = 0; + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + double t_fft_len = (double)this->fftLen; + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + d_fft_old += rxKDParam[j].bSwitch_KFFT * ((double)this->fftLen / 1024 * 2112 * (1000 / (double)this->fftPeriod) / 4); + d_fft_new += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod_ms) / 4); + sfcnt += d_fft_old; + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[j].SampleFreq * rxKDParam[j].ZDParam[j].bSwitch_ZDDC; + } + } + // unsigned long long pre_cnt = sfcnt - d_fft_old + d_fft_new; + // if (pre_cnt > ach_sf_count_max_hz + 1) + // { + // return -3; + // } + // sf_all = pre_cnt; + } + if (fftPeriod_ms >= 1 && fftPeriod_ms <= 1000) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_type, fftWin); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_len, fftLen); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_preiod, fftPeriod_ms); + this->fftWin = fftWin; + this->fftLen = fftLen; + this->fftPeriod = fftPeriod_ms; + ret = true; + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, 3); + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::getDevFFTParam(UHD_FFTWin &fftWin, UHD_FFTLen &fftLen, int &fftPeriod_ms) +{ + bool ret = true; + if (p_usrp.ctx != NULL) + { + fftWin = this->fftWin; + fftLen = this->fftLen; + fftPeriod_ms = this->fftPeriod; + } + return ret; +} +bool Device_UHD::setLedEnable(int led_num, bool bEnable) +{ + int mv_size = 0; + if (led_num == 0) + { + mv_size = 1; + } + if (led_num == 1) + { + mv_size = 4; + } + if (led_num == 2) + { + mv_size = 3; + } + if (led_num == 3) + { + mv_size = 2; + } + if (led_num == 4) + { + mv_size = 0; + } + if (led_num == 5) + { + mv_size = 5; + } + if (p_usrp.ctx != NULL) + { + if (bEnable) + { + this->led_val = led_val | (1 << mv_size); + } + else + { + this->led_val &= ~(1 << mv_size); + } + set_esun_rx_addr_val(p_usrp.ctx, addr_dev_led, led_val); + return true; + } + return false; +} +bool Device_UHD::getLedEnable(int led_num, bool &bEnable) +{ + if (p_usrp.ctx != NULL) + { + int mv_size = 0; + if (led_num == 0) + { + mv_size = 1; + } + if (led_num == 1) + { + mv_size = 4; + } + if (led_num == 2) + { + mv_size = 3; + } + if (led_num == 3) + { + mv_size = 2; + } + if (led_num == 4) + { + mv_size = 0; + } + if (led_num == 5) + { + mv_size = 5; + } + int enable_val = led_val >> mv_size & 0x01; + bEnable = enable_val; + return true; + } + return false; +} +int Device_UHD::setDevZDParam(string chStr, unsigned char ZDCh, long long centerfreq, int bw, int sample) +{ + stringstream ss; + ss << "ch_str:[" << chStr << "]\tzdch:[" << (int)ZDCh << "]\t\tcf:[" << centerfreq << "]\tbw:[" << bw << "]\tsf:[" << sample << "]" << endl; + MACROS_ERROR_CBK_CALL(ss.str().c_str()); + int ret = 0; + if (p_usrp.ctx != NULL) + { + try + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, 0); + int channel = chStr.find("0") != string::npos ? 0 : 1; + if (sample == 0) + { + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].CenterFreq = 0; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].BandWidth = 0; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq = 0; + } + else + { + int kddcSam = rxKDParam[channel].SampleFreq; + long long kddcCenter = rxKDParam[channel].CenterFreq; + int kddcBW = rxKDParam[channel].BandWidth; + int samIndex = ZDExtraMin; + for (int i = ZDExtraMin; i <= ZDExtraMax; i++) + { + if ((int)(kddcSam / pow(2, i + 0)) == sample) + { + samIndex = i; + break; + } + } + int samSum = 0; + for (auto zd : rxKDParam[channel].ZDParam) + { + samSum += zd.ZCH != ZDCh ? zd.SampleFreq : 0; + } + if (samIndex < ZDExtraMin || samSum + sample > ZDSamSum) + { + ret = -3; + } + if (centerfreq < 0 || centerfreq < kddcCenter - kddcBW / 2 || centerfreq > kddcCenter + kddcBW / 2) + { + ret = -1; + } + if (bw < (int)(kddcSam / pow(2, ZDExtraMax + 2) * 0.1) || bw > (int)(kddcSam / pow(2, ZDExtraMin + 2) * 0.8)) + { + ret = -2; + } + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + // unsigned long long pre_cnt = sfcnt - rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq + sample; + // if (pre_cnt > ach_sf_count_max_hz + 1) + // { + // return -3; + // } + // sf_all = pre_cnt; + } + if (ret == 0) + { + int freqkey = (int)((double)(centerfreq - rxKDParam[channel].CenterFreq) / kddcSam * pow(2, 32)); + long long old_cf = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].CenterFreq; + long long old_bw = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].BandWidth; + long long old_sf = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].CenterFreq = centerfreq; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].BandWidth = bw; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq = sample; + int zd_is_up = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].bSwitch_ZDDC; + { + int CH = ZDCh; + unsigned long long BandWidth = bw; + unsigned long long SampleFreq = sample; + unsigned long long CenterFreq = centerfreq; + int CtrWord = freqkey; + bool bSwitch = zd_is_up; + int chouqvlv_num = samIndex; + int chouqvlv = samIndex; + int zd_FilterCoef[128]; + memset(zd_FilterCoef, 0, 128 * sizeof(int)); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ch, ZDCh); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_df, chouqvlv); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_bw, BandWidth); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_cw, CtrWord); + if (!(old_cf != CenterFreq && old_bw == BandWidth && old_sf == sample)) + { + int zd_filterLen = SetFIRLpCoef2(zd_FilterCoef, 128, (double)BandWidth / SampleFreq, 18, 1.0); + + stringstream ss1; + ss1 << "设置zd滤波器系数" << endl; + MACROS_ERROR_CBK_CALL(ss1.str().c_str()); + + for (int i = 0; i < zd_filterLen; ++i) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_fc, zd_FilterCoef[i]); + } + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_sf, SampleFreq); + // set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ch, ZDCh); + } + ret = 0; + } + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, 3); + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::getDevZDParam(string chStr, unsigned char ZDCh, long long ¢erfreq, int &bw, int &sample) +{ + bool ret = true; + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + centerfreq = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].CenterFreq; + bw = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].BandWidth; + sample = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq; + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return ret; +} +void Device_UHD::getDevZDCenterRange(string chStr, long long &min, long long &max) +{ + int channel = chStr.find("0") != string::npos ? 0 : 1; + min = rxKDParam[channel].CenterFreq - rxKDParam[channel].BandWidth / 2; + min = std::max(min, 0LL); + max = rxKDParam[channel].CenterFreq + rxKDParam[channel].BandWidth / 2; +} +void Device_UHD::getDevZDBandWidthRange(string chStr, int &min, int &max) +{ + int channel = chStr.find("0") != string::npos ? 0 : 1; + min = rxKDParam[channel].SampleFreq / pow(2, ZDExtraMax + 2) * 0.1; + max = rxKDParam[channel].SampleFreq / pow(2, ZDExtraMin + 2) * 0.8; +} +vector Device_UHD::getDevZDSampleFreqRange(string chStr) +{ + vector vecSam; + int channel = chStr.find("0") != string::npos ? 0 : 1; + for (int i = ZDExtraMax; i >= ZDExtraMin; i--) + { + double exSam = rxKDParam[channel].SampleFreq / pow(2, i + 2); + if (exSam > ZDSamSum) + { + continue; + } + vecSam.push_back(exSam); + } + return vecSam; +} +bool old_need_sw; + +// TODO:need del +// typedef struct tmp_cache_t +// { +// void *pcache; +// int len; +// } tmp_cache_t; +// tmp_cache_t m_tmp_cache_arr[2]; + +void ThreadRecv_Func(ThreadRecvParam recvParam) +{ + ofstream ofs_datsrc1; + ofstream ofs_datsrc2; + // if (recvParam.channel == 0) + // { + // ofs_datsrc1.open("DatSrc1.dat", ios::binary); + // m_tmp_cache_arr[0].pcache = malloc(1024 * 1024 * 500); + // } + // else if (recvParam.channel == 1) + // { + // ofs_datsrc2.open("DatSrc2.dat", ios::binary); + // m_tmp_cache_arr[1].pcache = malloc(1024 * 1024 * 500); + // } + + Device_UHD *pThis = (Device_UHD *)recvParam.pThis; + int channel = recvParam.channel; + + if (pThis != NULL && pThis->p_usrp.ctx != NULL) + { + try + { + std::chrono::steady_clock::time_point befTime = std::chrono::steady_clock::now(); + long long iRecvedLen = 0; + struct iio_device *phy; + struct iio_channel *chan; + phy = iio_context_find_device(pThis->p_usrp.ctx, "adrv9002-phy"); + if (pThis->p_usrp.ctx) + { + if (channel == 0) + { + chan = iio_device_find_channel(phy, "voltage0", false); + iio_channel_attr_write(chan, "ensm_mode", "rf_enabled"); + + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + chan = iio_device_find_channel(phy, "voltage0", true); + iio_channel_attr_write(chan, "ensm_mode", "primed"); + + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + // tmp_block_size[channel] = my_iio_rx_block_point_size_big; + pThis->p_usrp.iio_rx_arr[channel].rx_buf = iio_device_create_buffer(pThis->p_usrp.iio_rx_arr[channel].rx_dev, tmp_block_size[channel], false); + } + else if (channel == 1) + { + chan = iio_device_find_channel(phy, "voltage1", false); + iio_channel_attr_write(chan, "ensm_mode", "rf_enabled"); + + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx2-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + chan = iio_device_find_channel(phy, "voltage1", true); + iio_channel_attr_write(chan, "ensm_mode", "primed"); + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx2-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + // tmp_block_size[channel]= my_iio_rx_block_point_size_big /8; + pThis->p_usrp.iio_rx_arr[channel].rx_buf = iio_device_create_buffer(pThis->p_usrp.iio_rx_arr[channel].rx_dev, tmp_block_size[channel], false); + } + // int ttret = iio_buffer_set_blocking_mode( pThis->p_usrp.iio_rx_arr[channel].rx_buf,false); + // int b =0; + } + void *buff_ptrs; + + while (pThis->bThread_Recv[channel]) + { + bool need_sleep = false; + + // tmp_sf_all = sf_all; + int nrxbytes = iio_buffer_refill(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + int a = 0; + buff_ptrs = iio_buffer_start(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + size_t recvSize = nrxbytes; // num_rx_samps * iPointSize * iChCount; + if (recvSize > 0 && nrxbytes <= tmp_block_size[channel] * 4) + { + + // if (channel == 0) + // { + // if (ofs_datsrc1.is_open()) + // { + // if (m_tmp_cache_arr[channel].pcache != NULL && m_tmp_cache_arr[channel].len < 300 * 1024 * 1024) + // { + // memcpy(m_tmp_cache_arr[channel].pcache + m_tmp_cache_arr[channel].len, (char *)buff_ptrs, recvSize); + // m_tmp_cache_arr[channel].len += recvSize; + // } + // if (m_tmp_cache_arr[channel].len >= 300 * 1024 * 1024) + // { + // ofs_datsrc1.write((char *)m_tmp_cache_arr[channel].pcache, m_tmp_cache_arr[channel].len); + // ofs_datsrc1.close(); + // free(m_tmp_cache_arr[channel].pcache); + // m_tmp_cache_arr[channel].pcache = NULL; + // } + // } + // } + // else if (channel == 1) + // { + // if (ofs_datsrc2.is_open()) + // { + // if (m_tmp_cache_arr[channel].pcache != NULL && m_tmp_cache_arr[channel].len <= 100 * 1024 * 1024) + // { + // memcpy(m_tmp_cache_arr[channel].pcache + m_tmp_cache_arr[channel].len, (char *)buff_ptrs, recvSize); + // m_tmp_cache_arr[channel].len += recvSize; + // } + // if (m_tmp_cache_arr[channel].len >= (20480000)) + // { + // ofs_datsrc2.write((char *)m_tmp_cache_arr[channel].pcache, m_tmp_cache_arr[channel].len); + // ofs_datsrc2.close(); + // free(m_tmp_cache_arr[channel].pcache); + // m_tmp_cache_arr[channel].pcache = NULL; + // } + // } + // } + + iRecvedLen += recvSize; + bool bCheckHead = pThis->ischeck_head; + if (bCheckHead) + { + int &iRecvBufSize = pThis->iRecvBufSize[channel]; // 缓存总大小 + int &iRecvBufPosi = pThis->iRecvBufPosi[channel]; // 缓存当前大小 + char *pDataBuf = (char *)pThis->pRecvBuf[channel]; // 缓存数据指针 + int length = recvSize; // 网络接收数据大小 + int lengthPosi = 0; // 网络接收数据已处理大小 + char *pData = (char *)buff_ptrs; // 网络数据指针 + char *pCheckData = NULL; // 检测包头的数据指针 + int posi = 0; + if (iRecvBufPosi > 0) + { + int needSize = iRecvBufSize - iRecvBufPosi; // 还需要补充数据量 + needSize = needSize > length ? length : needSize; + lengthPosi = needSize; + memcpy((char *)pDataBuf + iRecvBufPosi, pData, needSize); + iRecvBufPosi = needSize + iRecvBufPosi; // 当前缓存中数据长度 + pCheckData = pDataBuf; + } + else + { + iRecvBufPosi = length; + pCheckData = pData; + } + // 111 + while (pThis->bThread_Recv[channel] && posi < iRecvBufPosi) + { + if (posi + 4 < iRecvBufPosi) + { + if (((unsigned int *)(pCheckData + posi))[0] == 0xf628f6f6) + { + int packLen = (int)2112; + if (posi + packLen <= iRecvBufPosi) + { + if (pThis->callBackData != NULL) + { + pThis->callBackData(channel, pCheckData + posi, packLen); + if (*((char *)pCheckData + posi + 12) == 16) + { + char gps_info[60] = {0}; + strncpy(gps_info, pCheckData + posi + 64, 28); + pThis->gps_info = gps_info; + std::reverse(pThis->gps_info.begin(), pThis->gps_info.end()); + } + } + posi += packLen; + } + else + { + break; + } + } + else + { + posi += 1; + } + } + else + { + break; + } + } + // 如果有剩余数据 + if (posi < iRecvBufPosi) + { + memcpy(pDataBuf, pCheckData + posi, iRecvBufPosi - posi); + iRecvBufPosi = iRecvBufPosi - posi; + } + else + { + iRecvBufPosi = 0; + } + if (pCheckData != pData) + { + if (length - lengthPosi < 2112) + { + memcpy(pDataBuf + iRecvBufPosi, pData + lengthPosi, length - lengthPosi); + iRecvBufPosi += length - lengthPosi; + } + else + { + // 111 + posi = lengthPosi - iRecvBufPosi; + pCheckData = pData; + while (pThis->bThread_Recv[channel] && posi < length) + { + if (posi + 4 < length) + { + if (((unsigned int *)(pCheckData + posi))[0] == 0xf628f6f6) + { + int packLen = (int)2112; + if (posi + packLen <= length) + { + if (pThis->callBackData != NULL) + { + + pThis->callBackData(channel, pCheckData + posi, packLen); + if (*((char *)pCheckData + posi + 12) == 16) + { + char gps_info[60] = {0}; + strncpy(gps_info, pCheckData + posi + 64, 28); + pThis->gps_info = gps_info; + std::reverse(pThis->gps_info.begin(), pThis->gps_info.end()); + } + } + posi += packLen; + } + else + { + break; + } + } + else + { + posi += 1; + } + } + else + { + break; + } + } + // 如果有剩余数据 + if (posi < length) + { + memcpy(pDataBuf, pCheckData + posi, length - posi); + iRecvBufPosi = length - posi; + } + else + { + iRecvBufPosi = 0; + } + // 111 + } + } + } + else + { + pThis->callBackData(channel, buff_ptrs, (int)recvSize); + if (*((char *)buff_ptrs + 12) == 16) + { + char gps_info[60] = {0}; + strncpy(gps_info, (char *)buff_ptrs + 64, 28); + pThis->gps_info = gps_info; + std::reverse(pThis->gps_info.begin(), pThis->gps_info.end()); + } + } + } + else + { + need_sleep = true; + } + // bool need_big = false; + // for (int i = 0; i < 2; ++i) + // { + // if (pThis->rxKDParam[i].bSwitch_KDDC == true) + // { + // need_big = true; + // } + // for (int j = 0; j < 8; ++j) + // { + // if (pThis->rxKDParam[i].ZDParam[j].bSwitch_ZDDC == true) + // { + // need_big = true; + // break; + // } + // } + // if (need_big) + // { + // break; + // } + // } + // if (need_big == true && need_big != old_need_sw) + // { + // iio_buffer_destroy(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + // pThis->p_usrp.iio_rx_arr[channel].rx_buf = iio_device_create_buffer(pThis->p_usrp.iio_rx_arr[channel].rx_dev, tmp_block_size[channel], false); + // old_need_sw = need_big; + // } + // if (need_big == false && need_big != old_need_sw) + // { + // iio_buffer_destroy(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + // pThis->p_usrp.iio_rx_arr[channel].rx_buf = iio_device_create_buffer(pThis->p_usrp.iio_rx_arr[channel].rx_dev, tmp_block_size[channel], false); + // old_need_sw = need_big; + // } + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fRecvSpeed[channel] = (double)iRecvedLen / dur_ms.count() * 1000; + iRecvedLen = 0; + befTime = curTime; + } + + if (need_sleep) + { + if (channel == 1) + { + usleep(10000); + } + } + } + if (channel == 0) + { + chan = iio_device_find_channel(phy, "voltage0", false); + iio_channel_attr_write(chan, "ensm_mode", "primed"); + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + iio_buffer_destroy(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + } + else if (channel == 1) + { + chan = iio_device_find_channel(phy, "voltage1", false); + iio_channel_attr_write(chan, "ensm_mode", "primed"); + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx2-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + iio_buffer_destroy(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + } + } + catch (std::exception ex) + { + std::cout << ex.what() << std::endl; + } + } +} +double Device_UHD::getDevRecvSpeed(int channel) const +{ + return fRecvSpeed[channel]; +} +bool Device_UHD::isDevRecving(int channel) const +{ + return bThread_Recv[channel]; +} +void Device_UHD::startDevRecv(int channel) +{ + if (spThread_Recv[channel] != NULL) + { + bThread_Recv[channel] = false; + if (spThread_Recv[channel]->joinable()) + { + spThread_Recv[channel]->join(); + } + spThread_Recv[channel] = NULL; + if (pRecvBuf[channel] != NULL) + { + free(pRecvBuf[channel]); + pRecvBuf[channel] = NULL; + } + } + else + { + if (channel == 0) + { + tmp_block_size[channel] = my_iio_rx_block_point_size_big; + } + else + { + tmp_block_size[channel] = my_iio_rx_block_point_size_big / 8; + } + iRecvBufSize[channel] = tmp_block_size[channel] * 2; + iRecvBufPosi[channel] = 0; + pRecvBuf[channel] = malloc(iRecvBufSize[channel]); + bThread_Recv[channel] = true; + ThreadRecvParam recvParam; + recvParam.channel = channel; + recvParam.pThis = this; + // rx_kd_ddc_sw |= (1 << channel); + // set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, rx_kd_ddc_sw); + + send_all_sw &= ~(1 << channel); + set_esun_rx_addr_val(p_usrp.ctx, addr_tx_send_data_cw, send_all_sw); + + long long tmll = time(0); + + set_esun_rx_addr_val(p_usrp.ctx, addr_tm_set_l, tmll & 0xffffffff); + set_esun_rx_addr_val(p_usrp.ctx, addr_tm_set_h, tmll >> 32); + + spThread_Recv[channel] = std::make_shared(ThreadRecv_Func, recvParam); + // // 绑核 + // pthread_t pthread = spThread_Recv[channel]->native_handle(); + // cpu_set_t cpuset; + // CPU_ZERO(&cpuset); + // CPU_SET(channel, &cpuset); + // int ret = pthread_setaffinity_np(pthread, sizeof(cpu_set_t), &cpuset); + // if (0 != ret) + // { + // std::cout << strerror(errno) << std::endl; + // } + } +} +void Device_UHD::stopDevRecv(int channel) +{ + if (spThread_Recv[channel] != NULL) + { + bThread_Recv[channel] = false; + if (spThread_Recv[channel]->joinable()) + { + spThread_Recv[channel]->join(); + } + spThread_Recv[channel] = NULL; + } +} +double Device_UHD::getDevSendSpeed(int channel) const +{ + return fSendSpeed[channel]; +} +bool Device_UHD::isDevSending(int channel) const +{ + return bThread_Send[channel]; +} +void ThreadSend_Func(Device_UHD *pThis, unsigned long long send_cf, unsigned long long data_sf, int channel, char *filename, bool bWhile) +{ + if (pThis != NULL && pThis->p_usrp.ctx_isopen) + { + std::chrono::steady_clock::time_point befTime = std::chrono::steady_clock::now(); + long long iSendedLen0 = 0; + long long iSendedLen1 = 0; + char *pdata0; + char *pdata1; + struct iio_device *phy; + struct iio_channel *chan0; + struct iio_channel *chan1; + phy = iio_context_find_device(pThis->p_usrp.ctx, "adrv9002-phy"); + // 定义缓冲区 + char buffer0[1024]; + char buffer1[1024]; + std::vector content0; + std::vector content1; + size_t read_len0 = 0; + size_t read_len1 = 0; + // set_esun_rx_addr_val(p_usrp.ctx, addr_dev_ref_time, 5); + int insert_num = 0; + int cw_30Mhz = 0; + cw_30Mhz = ((double)send_cf / ((double)122880000) * pow(2, 32)); + for (int i = 0; i <= 10; i++) + { + if (((int)define_KDDCSampleMax1 / pow(2, i)) == data_sf) + { + insert_num = i; + break; + } + } + if (channel == 0) + { + pThis->send_all_sw |= (1 << channel); + } + if (channel == 1) + { + pThis->send_all_sw |= (1 << channel); + } + if (send_cf > 30 * MHz && send_cf < 6 * GHz) + { + pThis->send_chan_sw &= ~(1 << channel); + } + if (send_cf <= 30 * MHz && send_cf > 0) + { + pThis->send_chan_sw |= (1 << channel); + } + int ch_cw = pThis->send_chan_sw; + if (pThis->p_usrp.ctx) + { + + // pThis->rxKDParam[channel].bSwitch_KDDC = false; + // pThis->rxKDParam[channel].bSwitch_KFFT = false; + // for(int i = 0; i <8;++i) + // { + // pThis->rxKDParam[0].ZDParam[i].bSwitch_ZDDC = false; + + // } + + // int kz_val = 0; + // for(int i = 0; i <2;++i) + // { + // if(pThis->rxKDParam[i].Snapshot_Switch == true) + // { + + // kz_val |=1<rxKDParam[i].Snapshot_Switch + + // kz_val &=~(1<p_usrp.ctx, addr_rx_kd_ddc_data_sw, 0); + + // set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_rx_kd_ddc_data_sw, 0); + + if (send_cf > 30 * MHz && send_cf < 6 * GHz) + { + if (channel == 0) + { + + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + + chan0 = iio_device_find_channel(phy, "voltage0", false); + iio_channel_attr_write(chan0, "ensm_mode", "primed"); + + chan0 = iio_device_find_channel(phy, "voltage0", true); + iio_channel_attr_write(chan0, "ensm_mode", "rf_enabled"); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch0_insert_rate, insert_num); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + chan0 = iio_device_find_channel(phy, "altvoltage2", true); + iio_channel_attr_write_longlong(chan0, "TX1_LO_frequency", send_cf); + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + pThis->p_usrp.iio_tx_arr[channel].tx_buf = iio_device_create_buffer(pThis->p_usrp.iio_tx_arr[channel].tx_dev, my_iio_tx_block_point_size, false); + } + if (channel == 1) + { + + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx2-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + + chan1 = iio_device_find_channel(phy, "voltage1", false); + iio_channel_attr_write(chan1, "ensm_mode", "primed"); + + chan1 = iio_device_find_channel(phy, "voltage1", true); + iio_channel_attr_write(chan1, "ensm_mode", "rf_enabled"); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch1_insert_rate, insert_num); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + chan1 = iio_device_find_channel(phy, "altvoltage3", true); + iio_channel_attr_write_longlong(chan1, "TX2_LO_frequency", send_cf); + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx2-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + // pThis->p_usrp.iio_tx_arr[channel].tx_chan[1] = iio_device_find_channel(pThis->p_usrp.iio_tx_arr[channel].tx_dev, "voltage0", true); + // if (!iio_channel_is_enabled(pThis->p_usrp.iio_tx_arr[channel].tx_chan[1])) + // { + // iio_channel_enable(pThis->p_usrp.iio_tx_arr[channel].tx_chan[1]); + // } + pThis->p_usrp.iio_tx_arr[channel].tx_buf = iio_device_create_buffer(pThis->p_usrp.iio_tx_arr[channel].tx_dev, my_iio_tx_block_point_size, false); + } + } + else if (send_cf <= 30 * MHz && send_cf > 0) + { + if (channel == 0) + { + chan0 = iio_device_find_channel(phy, "voltage0", true); + iio_channel_attr_write(chan0, "ensm_mode", "primed"); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch0_insert_rate, insert_num); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch0_30MHz_cw, cw_30Mhz); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + pThis->p_usrp.iio_tx_arr[channel].tx_buf = iio_device_create_buffer(pThis->p_usrp.iio_tx_arr[channel].tx_dev, my_iio_tx_block_point_size, false); + } + if (channel == 1) + { + chan1 = iio_device_find_channel(phy, "voltage1", true); + iio_channel_attr_write(chan1, "ensm_mode", "primed"); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch1_insert_rate, insert_num); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch1_30MHz_cw, cw_30Mhz); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx2-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + // pThis->p_usrp.iio_tx_arr[channel].tx_chan[1] = iio_device_find_channel(pThis->p_usrp.iio_tx_arr[channel].tx_dev, "voltage0", true); + // if (!iio_channel_is_enabled(pThis->p_usrp.iio_tx_arr[channel].tx_chan[1])) + // { + // iio_channel_enable(pThis->p_usrp.iio_tx_arr[channel].tx_chan[1]); + // } + pThis->p_usrp.iio_tx_arr[channel].tx_buf = iio_device_create_buffer(pThis->p_usrp.iio_tx_arr[channel].tx_dev, my_iio_tx_block_point_size, false); + } + } + } + + if (channel == 0) + { + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) + { + std::cerr << "无法打开文件" << std::endl; + return; // 返回错误代码 + } + // 逐块读取文件内容 + while (file.read(buffer0, sizeof(buffer0))) + { + std::streamsize bytesRead = file.gcount(); // 获取实际读取的字节数 + content0.insert(content0.end(), buffer0, buffer0 + bytesRead); + } + // 处理最后一块可能不足1024字节的情况 + if (file) + { + std::streamsize bytesRead = file.gcount(); + content0.insert(content0.end(), buffer0, buffer0 + bytesRead); + } + // 关闭文件 + file.close(); + } + if (channel == 1) + { + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) + { + std::cerr << "无法打开文件" << std::endl; + return; // 返回错误代码 + } + // 逐块读取文件内容 + while (file.read(buffer1, sizeof(buffer1))) + { + std::streamsize bytesRead = file.gcount(); // 获取实际读取的字节数 + content1.insert(content1.end(), buffer1, buffer1 + bytesRead); + } + // 处理最后一块可能不足1024字节的情况 + if (file) + { + std::streamsize bytesRead = file.gcount(); + content1.insert(content1.end(), buffer1, buffer1 + bytesRead); + } + // 关闭文件 + file.close(); + } + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_data_cw, pThis->send_all_sw); + while (pThis->bThread_Send[channel]) + { + if (channel == 0) + { + size_t num_tx = iio_buffer_push(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + if (num_tx <= 0 || num_tx > my_iio_tx_block_point_size * 4) + { + continue; + } + pdata0 = (char *)iio_buffer_start(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + if (!pdata0) + { + continue; + } + if (content0.size() < num_tx) + { + break; + } + if (content0.size() - read_len0 < num_tx) + { + if (bWhile == true) + { + // read_len0 = 0; + + // 多余数据 + unsigned long long dysj = content0.size() - read_len0; + // 需要切割的头部数据 + unsigned long long need_cut_size = num_tx - dysj; + + ::memcpy(pdata0, content0.data() + read_len0, dysj); + ::memcpy(pdata0 + dysj, content0.data(), need_cut_size); + + read_len0 = need_cut_size; + iSendedLen0 += num_tx; + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fSendSpeed[channel] = (double)iSendedLen0 / dur_ms.count() * 1000; + iSendedLen0 = 0; + befTime = curTime; + } + + continue; + } + else + { + break; + } + } + ::memcpy(pdata0, content0.data() + read_len0, num_tx); + read_len0 += num_tx; + iSendedLen0 += num_tx; + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fSendSpeed[channel] = (double)iSendedLen0 / dur_ms.count() * 1000; + iSendedLen0 = 0; + befTime = curTime; + } + } + if (channel == 1) + { + size_t num_tx = iio_buffer_push(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + if (num_tx <= 0 || num_tx > my_iio_tx_block_point_size * 4) + { + continue; + } + pdata1 = (char *)iio_buffer_start(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + if (!pdata1) + { + continue; + } + if (content1.size() < num_tx) + { + break; + } + if (content1.size() - read_len1 < num_tx) + { + if (bWhile == true) + { + // read_len1 = 0; + + // 多余数据 + unsigned long long dysj = content1.size() - read_len1; + // 需要切割的头部数据 + unsigned long long need_cut_size = num_tx - dysj; + + ::memcpy(pdata1, content1.data() + read_len1, dysj); + ::memcpy(pdata1 + dysj, content1.data(), need_cut_size); + + read_len1 = need_cut_size; + iSendedLen1 += num_tx; + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fSendSpeed[channel] = (double)iSendedLen1 / dur_ms.count() * 1000; + iSendedLen1 = 0; + befTime = curTime; + } + + continue; + } + else + { + break; + } + } + ::memcpy(pdata1, content1.data() + read_len1, num_tx); + read_len1 += num_tx; + iSendedLen1 += num_tx; + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fSendSpeed[channel] = (double)iSendedLen1 / dur_ms.count() * 1000; + iSendedLen1 = 0; + befTime = curTime; + } + } + } + + //--------------------------------------------- + + pThis->send_chan_sw &= ~(1 << channel); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + + pThis->send_all_sw &= ~(1 << channel); + + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_data_cw, pThis->send_all_sw); + if (channel == 0) + { + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + chan0 = iio_device_find_channel(phy, "voltage0", true); + iio_channel_attr_write(chan0, "ensm_mode", "primed"); + } + if (channel == 1) + { + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx2-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + chan0 = iio_device_find_channel(phy, "voltage1", true); + iio_channel_attr_write(chan0, "ensm_mode", "primed"); + } + + iio_buffer_destroy(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + } +} +void Device_UHD::startDevSend(int channel, unsigned long long send_cf, unsigned long long data_sf, char *filename, bool bwhile) +{ + if (spThread_Send[channel] != NULL) + { + bThread_Send[channel] = false; + if (spThread_Send[channel]->joinable()) + { + spThread_Send[channel]->join(); + } + spThread_Send[channel] = NULL; + } + else + { + bThread_Send[channel] = true; + rx_kd_ddc_sw &= ~(1 << channel); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, rx_kd_ddc_sw); + spThread_Send[channel] = std::make_shared(ThreadSend_Func, this, send_cf, data_sf, channel, filename, bwhile); + } +} +void Device_UHD::stopDevSend(int channel) +{ + if (spThread_Send[channel] != NULL) + { + bThread_Send[channel] = false; + if (spThread_Send[channel]->joinable()) + { + spThread_Send[channel]->join(); + } + spThread_Send[channel] = NULL; + } +} +string Device_UHD::getGPSInfo() +{ + + return this->gps_info; +} diff --git a/module/dll_source_lzy/114/out/lib/device_uhd_cpp.h b/module/dll_source_lzy/114/out/lib/device_uhd_cpp.h new file mode 100644 index 0000000..54febef --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/device_uhd_cpp.h @@ -0,0 +1,144 @@ +#ifndef DEVICE_UHD_H +#define DEVICE_UHD_H + +#include "device_uhd_struct.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +using namespace std; +std::string get_UHD_ParamType(UHD_ParamType v); +class Device_UHD +{ + public: + uhd::device_addr_t devAddr; // 设备地址 + bool bFindedDevice; + std::shared_ptr spThread_Recv[2]; + std::shared_ptr spThread_Send[2]; + uhd::property_tree::sptr pTree; + int kd_centerfreq_sw; + double fParamMin; + double fParamMax; + UHD_FFTWin fftWin; // 窗类型 + UHD_FFTLen fftLen; // 窗长度 + int fftPeriod; // 频谱周期 1-1000,单位ms + UHD_KDParam rxKDParam[2]; // rx宽带通道参数 + UHD_KDParam txKDParam[2]; // tx宽带通道参数 + int FilterCoef[256]; // 滤波器系数数组 + public: + std::string gps_info; + int led_val; + + public: + //! Device type, used as a filter in make + // enum device_filter_t { ANY, USRP, CLOCK }; + bool isDevOpened() const; + uhd::device_addrs_t findDevices(); + bool openDevice(uhd::device_addr_t addr); + // 关闭设备 + void closeDevice(); + // 获取设备信息 + string findDevInfos(); + // 获取设备信息2 + string findDevInfos2(); + // 获取设备树 + string getDevTree(); + // 设置时钟源 + bool setDevClockSource(string sourceStr); + // 获取时钟源 + string getDevClockSource(); + bool setDevInClock(double clockFreq); + void getDevInClockRange(); + double getDevInClock(); + size_t getDevRXChCount(); + size_t getDevTXChCount(); + bool setDevParam(string chStr, UHD_ParamType paramType, double paramValue); + double getDevParam(string chStr, UHD_ParamType paramType); + void getDevParamRange(string chStr, UHD_ParamType paramType, double ¶mMin, double ¶mMax); + double getDevParamMin() const; + double getDevParamMax() const; + bool setAllAntenna(); + bool setDevDDCParam_Any(string chStr, unsigned int jcqOffset, unsigned int jcqData); + bool getDevDDCParam_Any(string chStr, unsigned int jcqOffset, unsigned int &jcqData); + bool setDevDataSwitch(string chStr, UHD_ParamType dataType, unsigned char ZDCh, bool bSwitch); + bool getDevDataSwitch(string chStr, UHD_ParamType dataType, unsigned char ZDCh, bool &bSwitch); + // 采样率为0,表示该通道停止使用 + bool ischeck_head = true; + int setDevZDParam(string chStr, unsigned char ZDCh, long long centerfreq, int bw, int sample); + // 采样率为0,表示该通道未使用 + bool getDevZDParam(string chStr, unsigned char ZDCh, long long ¢erfreq, int &bw, int &sample); + void getDevZDCenterRange(string chStr, long long &min, long long &max); + void getDevZDBandWidthRange(string chStr, int &min, int &max); + vector getDevZDSampleFreqRange(string chStr); + bool setDevFFTParam(UHD_FFTWin fftWin, UHD_FFTLen fftLen, int fftPeriod_ms); + bool getDevFFTParam(UHD_FFTWin &fftWin, UHD_FFTLen &fftLen, int &fftPeriod_ms); + + bool bThread_Recv[2] = {0, 0}; + double fRecvSpeed[2] = {0, 0}; + void *pRecvBuf[2] = {0, 0}; + int iRecvBufSize[2] = {0, 0}; + int iRecvBufPosi[2] = {0, 0}; + double getDevRecvSpeed(int channel) const; + bool isDevRecving(int channel) const; + void startDevRecv(int channel); + void stopDevRecv(int channel); + double getDevSendSpeed(int channel) const; + bool isDevSending(int channel) const; + bool bThread_Send[2] = {0, 0}; + double fSendSpeed[2] = {0, 0}; + + int send_chan_sw = 0; + int send_all_sw = 0; + int rx_kd_ddc_sw = 0; + + void startDevSend(int channel, unsigned long long send_cf, unsigned long long data_sf, char *filename, bool bwhile); + void stopDevSend(int channel); + // uhd::usrp::multi_usrp::sptr pUSRP1; + uhd::usrp::multi_usrp::sptr p_usrp; + std::function callBackData; + + std::string getGPSInfo(); + bool setLedEnable(int led_num, bool bEnable); + bool getLedEnable(int led_num, bool &bEnable); + std::string get_UHD_FFTWin(UHD_FFTWin v) + { + /* + jxc =0,// 矩形窗 = 0, + hnc = 1,// 汉宁窗 = 1, + hmc = 2, // 海明窗 = 2, + blkmc = 3,// 布莱克曼窗 = 3, + */ + string tmp; + switch (v) + { + case jxc: { + tmp = "jxc"; + } + break; + case hnc: { + tmp = "hnc"; + } + break; + case hmc: { + tmp = "hmc"; + } + break; + case blkmc: { + tmp = "blkmc"; + } + break; + + default: + break; + } + return tmp; + }; + +}; +#endif // DEVICE_UHD_H diff --git a/module/dll_source_lzy/114/out/lib/device_uhd_struct.h b/module/dll_source_lzy/114/out/lib/device_uhd_struct.h new file mode 100644 index 0000000..800605b --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/device_uhd_struct.h @@ -0,0 +1,319 @@ +#ifndef device_uhd_struct_h +#define device_uhd_struct_h +#include +#include +#include +#ifdef __linux__ +#include "iio.h" +#include +#endif +#ifdef WIN32 +#include +#endif +#include +using namespace std; + +#pragma pack(push) +#pragma pack(1) +namespace uhd +{ +typedef std::string fs_path; +class device_addr_t +{ + public: + std::string uri; + std::string to_string(void) const { return uri; } +}; +typedef std::vector device_addrs_t; +namespace usrp +{ +namespace multi_usrp +{ +typedef struct rx_parm +{ + struct iio_device *rx_dev = nullptr; + struct iio_channel *rx_chan[2]; + struct iio_buffer *rx_buf; + bool rx_chan_is_enable; + bool rx_buf_is_enable; +} rx_parm; +typedef struct tx_parm +{ + struct iio_device *tx_dev = nullptr; + struct iio_channel *tx_chan[2]; + struct iio_buffer *tx_buf; + bool tx_chan_is_enable; + bool tx_buf_is_enable; +} tx_parm; +typedef struct sptr +{ + struct iio_context *ctx = nullptr; + bool ctx_isopen; + struct rx_parm iio_rx_arr[2]; + struct tx_parm iio_tx_arr[2]; + device_addrs_t uri_list; + std::string cur_sourceStr; + double cur_clockFreq; + int rx0_power_reference; + int rx1_power_reference; + int tx0_power_reference; + int tx1_power_reference; +} sptr; +} // namespace multi_usrp +} // namespace usrp +namespace property_tree +{ +typedef std::string sptr; +} +} // namespace uhd +#pragma pack(pop) +#define ZDSamSum 200000000 // 8路窄带采样率之和 +#define ZDChMax 8 // 每路大通道下窄带通道总数 +enum UHD_ParamType : int +{ + CenterFreq = 0, + BandWidth = 1, + SampleFreq = 2, + Gain = 3, + PowerRef = 4, + KDDCSwitch = 5, + KFFTSwitch = 6, + ZDDCSwitch = 7, + ZDDCParam = 8, + KFFTParam = 9, + ClockSource = 10, + SnapshotSwitch = 11, + Snapshot_tm_s = 12, +}; +enum UHD_FFTWin : unsigned char +{ + jxc = 0, // 矩形窗 = 0, + hnc = 1, // 汉宁窗 = 1, + hmc = 2, // 海明窗 = 2, + blkmc = 3, // 布莱克曼窗 = 3, +}; +enum UHD_FFTLen : unsigned int +{ + _1024 = 1024, + _2048 = 2048, + _4096 = 4096, + _8192 = 8192, + _16384 = 16384, + _32768 = 32768, +}; +#pragma pack(push) +#pragma pack(1) +struct UHD_ZDParam +{ + unsigned char KCH = 0; // 宽带通道号 + unsigned short ZCH = 0; // 窄带通道号 + long long CenterFreq = 0; // 中频 + int BandWidth = 0; // 带宽 + int SampleFreq = 0; // 采样率 + int ExtraPer = 1024; // 抽取率 4-4096,2的指数 + bool bSwitch_ZDDC = false; +}; +struct UHD_KDParam +{ + unsigned char KCH = 0; // 宽带通道号 + long long CenterFreq = 10000000; + + int BandWidth = 160000000; + int SampleFreq = 200000000; + + bool bSwitch_6GCH = false; + + bool bSwitch_KDDC = false; + bool bSwitch_KFFT = false; + double gain = 0; + UHD_ZDParam ZDParam[ZDChMax]; + int ExtraPer; + bool Snapshot_Switch = false; + int Snapshot_tm_s = 0; + + unsigned long long old_sf; + unsigned long long old_cf; + unsigned long long old_bw; +}; +#pragma region 枚举/结构体 + +/** @brief TODO: 时码类型枚举 + * @enum LibWZ57_Enum_TimeType + */ +enum Enum_TimeType : uint8_t +{ + Enum_TimeType_Invlid = 0, /**< 无效的*/ + Enum_TimeType_IRIG_B = 1, /**< IRIG-B*/ + Enum_TimeType_RMC = 2, /**< RMC*/ + Enum_TimeType_ABSCNTTM = 3, /**< 绝对计数时间*/ + Enum_TimeType_REFCNTTM = 4, /**< 相对计数时间*/ +}; +typedef struct struct_DateTime +{ + + Enum_TimeType TimeType; /**< 10 时码类型*/ + + /** @brief 10个字节的时码信息 + * @details + * 根据 @ref LibWZ57_Enum_TimeType TimeType,对应不同的 联合体 + */ + union TimeInfo { + ///@brief IRIG-B + ///@struct IRIGB_Head + struct IRIGB_Head + { + uint8_t Year; /**< 年 字节1*/ + uint16_t Day; /**< 日 字节2*/ + uint8_t Hour; /**< 时 字节1*/ + uint8_t Minute; /**< 分 字节1*/ + uint8_t Second; /**< 秒 字节1*/ + uint32_t NanoSec; /**< 纳 字节4*/ + } IRIGB_Head; + + ///@brief RMC + ///@struct RMC_Head + struct RMC_Head + { + uint8_t Year; /**< 年 字节1*/ + uint8_t Month; /**< 月 字节1*/ + uint8_t Day; /**< 日 字节1*/ + uint8_t Hour; /**< 时 字节1*/ + uint8_t Minute; /**< 分 字节1*/ + uint8_t Second; /**< 秒 字节1*/ + uint32_t NanoSec; /**< 纳 字节4*/ + } RMC_Head; + + ///@brief 绝对计数时间 + ///@struct ABSCNTTM_Head + struct ABSCNTTM_Head + { + ///@brief 秒 字节6; 基准秒计数时间+相对秒计数时间 单位:秒 + uint8_t Second[6]; + ///@brief 纳秒 字节4; + uint32_t NanoSec; + } ABSCNTTM_Head; + + ///@brief 相对计数时间 + ///@struct REFCNTTM_Head + struct REFCNTTM_Head + { + ///@brief 秒 字节6; 相对秒计数时间单位:秒 + uint8_t Second[6]; + ///@brief 纳秒 字节4; + uint32_t NanoSec; + } REFCNTTM_Head; + } TimeInfo; + +} struct_DataTime; + +#pragma region 枚举 +/***************************** + *数据类型 + *****************************/ +enum UHD_Enum_DataType : unsigned char +{ + zd_ddc = 1, + kd_ddc = 2, + kd_fft = 3, + GPS_Info = 16, + // 窄带DDC = 0x01, + // 宽带DDC = 0x02, + // 宽带FFT = 0x03, +}; +#pragma endregion +#pragma region 结构体 +#pragma region UHD_Struct_KFFT UHD_Struct_KDDC UHD_Struct_ZDDC +///

+/// 宽带FFT头 +/// +typedef struct +{ + /// + /// 帧标识 6 0x2828F628F6F6 固定值 + /// + unsigned int Head; + /// + /// + /// + unsigned short Head2; + /// + /// 协议版本 1 0x80 + /// + unsigned char DealVersion; + /// + /// 帧头长度 1 64 + /// + unsigned char FrameHeadLen; + /// + /// 数据长度 2 2048 + /// + unsigned short DataLen; + /// + /// 有效数据长度 2 2048 + /// + unsigned short UsefullLen; + /// + /// 数据类型 1 + /// + UHD_Enum_DataType DataType; + unsigned char Other1; + /// + /// 通道号 + /// + unsigned char Channel; + unsigned char Other2; + unsigned short ZDChannel; + /// + /// 帧计数 2 0 ~ 65535 循环递增 + /// + unsigned short FrameIndex; + /// + /// 帧起止标识 1 0x0 + /// + unsigned char FrameBeginFlag; + struct_DateTime DateTm; + /// + /// 采样率 4 单位Hz + /// + unsigned int SampleFreq_Hz; + /// + /// 带宽 4 单位Hz + /// + unsigned int BandWidth_Hz; + /// + /// 中心频点 4 单位Hz,低位 + /// + unsigned int CenterFreq_Hz_Low; + /// + /// 中心频点 2 单位Hz,高位 + /// + unsigned short CenterFreq_Hz_High; + union { + unsigned char other5[18]; + struct + { + unsigned char other4[6]; + /// + /// FFT长度 65536 + /// + int FFTPoint; + unsigned char other5[2]; + float other6; + /// + /// 段内帧号 + /// + unsigned short SubFrame; + } FFT; + } Param; + // 数据 2048 共1024个数据,1个数据2字节。 +} UHD_Struct_KFFT, UHD_Struct_KDDC, UHD_Struct_ZDDC; + + +#pragma endregion +#pragma endregion +#pragma pack(pop) + + +#pragma endregion + +#endif diff --git a/module/dll_source_lzy/114/out/lib/error_callback.cpp b/module/dll_source_lzy/114/out/lib/error_callback.cpp new file mode 100644 index 0000000..b971ff9 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/error_callback.cpp @@ -0,0 +1,43 @@ +#include "error_callback.h" +#include +#include +#include + +error_cbk_t g_error_callback = nullptr; + +static std::mutex g_error_mutex; + +const char *extract_filename_in_cbk(const char *full_path) +{ + if (!full_path) + return "unknow_file"; +#ifdef _WIN32 + const char *pos = strrchr(full_path, '\\'); +#endif +#ifdef __linux__ + const char *pos = strrchr(full_path, '/'); + +#endif + return (pos) ? (pos + 1) : full_path; +} +void error_callback_call(const char *file_name, const char *func_name, const char *err_msg) +{ + + g_error_mutex.lock(); + if (g_error_callback == nullptr) + { + const char * pure_file = extract_filename_in_cbk(file_name); + std::cout << "[" << func_name << "]==>\t" << err_msg; + //std::cout << "[默认打印]:" << "FILE:[" << pure_file << "]FUNC:[" << func_name << "]==>\t" << err_msg; + } + else + { + g_error_callback(file_name, func_name, err_msg); + } + g_error_mutex.unlock(); +} + +void set_error_cbk(error_cbk_t cbk) +{ + g_error_callback = cbk; +} diff --git a/module/dll_source_lzy/114/out/lib/error_callback.h b/module/dll_source_lzy/114/out/lib/error_callback.h new file mode 100644 index 0000000..4f0a28a --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/error_callback.h @@ -0,0 +1,17 @@ +#ifndef __ERROR_CALLBACK_H__ +#define __ERROR_CALLBACK_H__ + +#include "makelib_common.h" +typedef void (*error_cbk_t)(const char *file_name ,const char *func_name,const char *err_msg); + +extern error_cbk_t g_error_callback; + +ex_declare void error_callback_call( const char *file_name ,const char *func_name,const char *err_msg); +ex_declare const char *extract_filename_in_cbk(const char *full_path); +ex_declare void set_error_cbk(error_cbk_t cbk); + + +#define MACROS_ERROR_CBK_CALL(msg) error_callback_call(__FILE__,__func__,msg) + + +#endif // __ERROR_CALLBACK_H__ \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/hulc/Bit.cpp b/module/dll_source_lzy/114/out/lib/hulc/Bit.cpp new file mode 100644 index 0000000..2965e78 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/Bit.cpp @@ -0,0 +1,761 @@ +#include "Bit.h" +#include +#include +#include +#include +#include +// 2025-01-02 05:01:50 [info] 1a480118 9607f81e f020 3a24 67761dbd 1a9ecbf8 5655c75b 00601307 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _USE_GTEST +#include +using namespace yC; +#endif +constexpr std::array make_hex_to_val() { + std::array t{255}; // 初始化为255 + for (uint8_t i = '0'; i <= '9'; ++i) t[i] = i - '0'; + for (uint8_t i = 'A'; i <= 'F'; ++i) t[i] = i - 'A' + 10; + for (uint8_t i = 'a'; i <= 'f'; ++i) t[i] = i - 'a' + 10; + return t; +} +constexpr std::array make_val_to_hex(bool uppercase) { + std::uint8_t s = uppercase ? 'A' : 'a'; + std::array t{}; + for (uint8_t i = 0; i < 10; ++i) { + t[i] = '0' + i; // '0' - '9' + } + for (uint8_t i = 10; i < 16; ++i) { + t[i] = s + (i - 10); // 'a' - 'f' 'A - F' + } + return t; +} +static constexpr std::array val_to_upper_hex = make_val_to_hex(true); +static constexpr std::array val_to_lower_hex = make_val_to_hex(false); +static constexpr std::array hex_to_val = make_hex_to_val(); +constexpr const char* hex_lut_lower = "0123456789abcdef"; +constexpr const char* hex_lut_upper = "0123456789ABCDEF"; +constexpr const char* big_dian_hex_to_bin[16] = { + "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", + "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111" +}; +constexpr const char* small_dian_hex_to_bin[16] = { + "0000", "1000", "0100", "1100", "0010", "1010", "0110", "1110", + "0001", "1001", "0101", "1101", "0011", "1011", "0111", "1111" +}; +constexpr std::array, 256> make_xor_table() { + std::array, 256> t{}; + for (size_t i = 0; i < 256; ++i) { + for (size_t j = 0; j < 256; ++j) { + t[i][j] = 255; + } + } + for (size_t i = 0; i < 16; ++i) { + for (size_t j = 0; j < 16; ++j) { + t[val_to_lower_hex[i]][val_to_lower_hex[j]] = i ^ j; + t[val_to_upper_hex[i]][val_to_upper_hex[j]] = i ^ j; + } + } + return t; +} + +static constexpr std::array, 256> xor_table = make_xor_table(); +inline size_t bin4_to_hex(bool big_byte_order, const char* a) { + static size_t small_base[]{1, 2, 4, 8}; + static size_t big_base[]{8, 4, 2, 1}; + auto cur_dian = big_byte_order ? big_base : small_base; + size_t ret = 0; + for (size_t i = 0; i < 4; ++i) { + auto cur = size_t(a[i] - '0'); + ret += cur_dian[i] * cur; + } + return ret; +} + + + +namespace yC { + const bool is_big_endian = []() { + uint16_t x = 0x0102; // 设置一个已知的 16-bit 值 + auto* p = reinterpret_cast(&x); + return p[0] == 0x01; // 如果 p[0] 为 0x01,说明是大端字节序 + }(); + std::vector split(const std::string& str, const std::string& delimiter) { + std::vector tokens; + size_t start = 0; + size_t end = str.find(delimiter); + if (end == std::string::npos) return {}; + while (end != std::string::npos) { + tokens.push_back(str.substr(start, end - start)); + start = end + 1; + end = str.find(delimiter, start); + } + tokens.push_back(str.substr(start)); // Add the last token + return tokens; + } + std::string bin2mem(const std::string& bin) { + const size_t len = bin.size(); + if (len % 8 != 0) { + throw std::invalid_argument("binary string length must be a multiple of 8"); + } + std::string result; + result.reserve(len / 8); // 提前分配 + for (size_t i = 0; i < len; i += 8) { + uint8_t byte = 0; + for (size_t j = 0; j < 8; ++j) { + char bit = bin[i + j]; + if (bit == '1') { + byte |= (1 << (7 - j)); + } else if (bit != '0') { + std::ostringstream oss; + oss << "Invalid bit character '" << bit << "' at position " << (i + j); + throw std::invalid_argument(oss.str()); + } + } + result.push_back(static_cast(byte)); + } + return result; + } + + void bin2mem(void* dest, const char* data, size_t size) { + if (size % 8 != 0) { + throw std::invalid_argument("Binary string length must be a multiple of 8"); + } + uint8_t* byte_dest = static_cast(dest); // 将 dest 转换为字节指针 + for (size_t i = 0; i < size; i += 8) { + uint8_t byte = 0; + for (size_t j = 0; j < 8; ++j) { + char bit = data[i + j]; + if (bit == '1') { + byte |= (1 << (7 - j)); // 设置相应的位 + } else if (bit != '0') { + std::ostringstream oss; + oss << "Invalid bit character '" << bit << "' at position " << (i + j); + throw std::invalid_argument(oss.str()); + } + } + byte_dest[i / 8] = byte; // 将转换后的字节写入目标内存 + } + } + + void hex2mem_Ex(void* dest, const char* data, size_t size) { + // 判断输入 hex 数据的长度是否为偶数 + if (size % 2 != 0) { + throw std::invalid_argument("hex string length must be even"); + } + // 填充目标数据 + for (size_t i = 0; i < size; i += 2) { + unsigned char hi = data[i]; + unsigned char lo = data[i + 1]; + auto hi_val = hex_to_val[hi]; + auto lo_val = hex_to_val[lo]; + + if (hi_val == 255 || lo_val == 255) { + std::ostringstream oss; + oss << "hex:[" << data << "] Invalid hex: [" << data[i] << data[i + 1] << "] at pos " << i; + throw std::invalid_argument(oss.str()); + } + + // 填充目标内存 + unsigned char byte = (hi_val << 4) | lo_val; + std::memcpy(static_cast(dest) + i / 2, &byte, 1); + } + } + + void mem2hex_Ex2(void* dest, void* mem, size_t num, bool uppercase, const char* middle, size_t middle_len) { + assert(middle != nullptr); + + const char* lut = uppercase ? hex_lut_upper : hex_lut_lower; + if (middle_len == std::numeric_limits::max()) { + middle_len = (middle != nullptr) ? std::strlen(middle) : 0; + } + char* dest_ptr = static_cast(dest); + for (size_t i = 0; i < num; ++i) { + unsigned char c = static_cast(mem)[i]; + *dest_ptr++ = lut[c >> 4]; + *dest_ptr++ = lut[c & 0xF]; + // 添加分隔符 + if (middle_len > 0 && i + 1 < num) { + std::copy(middle, middle + middle_len, dest_ptr); + dest_ptr += middle_len; + } + } + } + + void hex2bin_Ex(void* dest, const char* data, size_t size, bool big_dian) { + auto& dian = big_dian ? big_dian_hex_to_bin : small_dian_hex_to_bin; + for (size_t i = 0; i < size; i++) { + auto str = dian[hex_to_val[(unsigned char)(data[i])]]; + std::memmove(static_cast(dest) + i * 4, str, 4); + } + } + + void + bin2hex_Ex(void* dest, const char* d, size_t size, bool big_dian, bool uppercase) { + const char* lut = uppercase ? hex_lut_upper : hex_lut_lower; + auto tt = get_bin2hex_size(size); + auto n1 = size; + auto n2 = tt * 4; + char cur[4] = {'0', '0', '0', '0'}; + auto r = static_cast(dest); + if (!big_dian) { + // 小端 补充后导0 + for (size_t i = 0; i < n2; i++) { + auto j = i%4; + cur[j] = i >= n1 ? '0' : d[i]; + if (j == 3) { + auto val = bin4_to_hex(false, cur); + r[tt - 1 - i/4] = lut[val]; + } + } + } else { + // 大端模式:补充前导0 + for (size_t i = 0; i < n2; i++) { + // 对应位置 + auto j = i%4; + cur[j] = i >= n1 ? '0' : d[n1 - 1 - i]; + if (j == 3) { + auto val = bin4_to_hex(false, cur); + r[tt - 1 - i/4] = lut[val]; + } + } + } + } +#ifdef _USE_GTEST + TEST(Bit2223, bin2hex_Ex) { + // EXPECT_EQ(bin2hex("01101110"), "6E"); + // + { + // 0110 1110 + // 6 E + std::string it = "1101110"; + auto size = get_bin2hex_size(it.size()); + std::string res; + res.resize(size); + bin2hex_Ex(res.data(), it.c_str(), it.size(), true, true); + EXPECT_STREQ(res.c_str(), "6E"); + } + { + // 1101 1100 + // B 3 + + std::string it = "1101110"; + auto size = get_bin2hex_size(it.size()); + std::string res; + res.resize(size); + bin2hex_Ex(res.data(), it.c_str(), it.size(), false, true); + + // std::cout << res << std::endl; + EXPECT_STREQ(res.c_str(), "3B"); + } + + + } +#endif + + + void xor_bin_Ex(void* dest, const char* s1, size_t n1, const char* s2, size_t n2, bool big_dian) { + if (n1 > n2) { + std::swap(n1, n2); + std::swap(s1, s2); + } + // n1 <= n2 + char* r = static_cast(dest); + if (!big_dian) { + // 小端 补充后导0 + for (size_t i = 0; i < n1; i++) { + r[i] = s1[i] == s2[i] ? '0' : '1'; + } + for (size_t i = n1; i < n2; i++) { + r[i] = '0' == s2[i] ? '0' : '1'; + } + } else { + // 大端模式:补充前导0 + for (size_t i = 0; i < n1; i++) { + // 对应位置 + auto c1 = s1[n1 - 1 - i]; + auto c2 = s2[n2 - 1 - i]; + r[n2 - 1 - i] = c1 == c2 ? '0' : '1'; // 对应位异或 + } + for (size_t i = n1; i < n2; i++) { + auto c1 = '0'; + auto c2 = s2[n2 - 1 - i]; + r[n2 - 1 - i] = c1 == c2 ? '0' : '1'; // 对应位异或 + } + } + } +#ifdef _USE_GTEST + TEST(Bit2223, xor_bin_Ex) { + const char* s1 = "10"; + const char* s2 = "111"; + std::string res; + res.resize(3); + xor_bin_Ex(res.data(), s1, 2, s2, 3, true); + EXPECT_STREQ(res.c_str(), "101"); + xor_bin_Ex(res.data(), s1, 2, s2, 3, false); + EXPECT_STREQ(res.c_str(), "011"); + xor_bin_Ex(res.data(), s2, 3, s1, 2, false); + EXPECT_STREQ(res.c_str(), "011"); + + s2 = "1111"; + res.resize(4); + // 0010 + // 1111 + // 1101 + xor_bin_Ex(res.data(), s1, 2, s2, 4, true); + EXPECT_STREQ(res.c_str(), "1101"); + } +#endif + + + + void xor_hex_Ex(void* dest, const char* s1, size_t n1, const char* s2, size_t n2, bool big_dian, bool uppercase) { + if (n1 > n2) { + std::swap(n1, n2); + std::swap(s1, s2); + } + auto& val_to_hex = uppercase ? val_to_upper_hex : val_to_lower_hex; + // n1 <= n2 + auto r = static_cast(dest); + if (!big_dian) { + // 小端 补充后导0 + for (size_t i = 0; i < n1; i++) { + //r[i] = s1[i] == s2[i] ? '0' : '1'; + auto c1 = s1[i]; + auto c2 = s2[i]; + r[i] = val_to_hex[xor_table[c1][c2]]; + } + for (size_t i = n1; i < n2; i++) { + //r[i] = '0' == s2[i] ? '0' : '1'; + auto c1 = '0'; + auto c2 = s2[i]; + r[i] = val_to_hex[xor_table[c1][c2]]; + } + } else { + // 大端模式:补充前导0 + for (size_t i = 0; i < n1; i++) { + // 对应位置 + auto c1 = s1[n1 - 1 - i]; + auto c2 = s2[n2 - 1 - i]; + //r[n2 - 1 - i] = c1 == c2 ? '0' : '1'; // 对应位异或 + auto idx = xor_table[c1][c2]; + r[n2 - 1 - i] = val_to_hex[idx]; // 对应位异或 + } + for (size_t i = n1; i < n2; i++) { + auto c1 = '0'; + auto c2 = s2[n2 - 1 - i]; + auto idx = xor_table[c1][c2]; + //r[n2 - 1 - i] = c1 == c2 ? '0' : '1'; // 对应位异或 + r[n2 - 1 - i] = val_to_hex[idx]; // 对应位异或 + } + } + } +#ifdef _USE_GTEST + TEST(Bi222, xor_hex_Ex) { + { + const char* s1 = "10"; + const char* s2 = "111"; + std::string res; + res.resize(3); + xor_hex_Ex(res.data(), s1, 2, s2, 3, true, true); + EXPECT_STREQ(res.c_str(), "101"); + xor_hex_Ex(res.data(), s1, 2, s2, 3, false, true); + EXPECT_STREQ(res.c_str(), "011"); + xor_hex_Ex(res.data(), s2, 3, s1, 2, false, true); + EXPECT_STREQ(res.c_str(), "011"); + } + { + const char* s1 = "ab"; + const char* s2 = "111"; + std::string res; + res.resize(3); + // 0ab 0000 0110 0111 + // 111 0001 0001 0001 + // 1ba 0001 0111 0110 + xor_hex_Ex(res.data(), s1, 2, s2, 3, true, true); + EXPECT_STREQ(res.c_str(), "1BA"); + + + + } + } +#endif + + // 将字节序从大端转换到当前平台字节序,并复制到目标内存中 + void big_endian_2_platform(const std::string& big_endian, char* dest) { + // 先复制原始数据 + std::string result = big_endian; + // 如果当前平台是小端,反转数据 + if (!is_big_endian) { + std::reverse(result.begin(), result.end()); + } + // 将转换后的数据拷贝到目标内存中 + std::memcpy(dest, result.data(), result.size()); + } + std::string big_endian_2_platform(const std::string& big_endian) { + std::string result; + result.resize(big_endian.size()); + big_endian_2_platform(big_endian, result.data()); + return result; + } + // 将字节序从小端转换到当前平台字节序,并复制到目标内存中 + void little_endian_2_platform(const std::string& little_endian, char* dest) { + // 先复制原始数据 + std::string result = little_endian; + // 如果当前平台是大端,反转数据 + if (is_big_endian) { + std::reverse(result.begin(), result.end()); + } + // 将转换后的数据拷贝到目标内存中 + std::memcpy(dest, result.data(), result.size()); + } + std::string little_endian_2_platform(const std::string& little_endian) { + std::string result; + result.resize(little_endian.size()); + big_endian_2_platform(little_endian, result.data()); + return result; + } + // 将平台字节序转换为大端字节序 + std::string platform_2_big_endian(void* t, int size) { + char* memory = reinterpret_cast(t); + // for (int i = 0; i < size; ++i) { + // std::cout << (int)memory[i] << std::endl; + // } + std::string result(memory, memory + size); + // 如果当前平台是小端,反转字符串,使其成为大端字节序 + //std::cout << "platform_2_big_endian start " << memory2hex(result) << std::endl; + if (!is_big_endian) { + std::reverse(result.begin(), result.end()); + } + //std::cout << "platform_2_big_endian end " << memory2hex(result) << std::endl; + return result; + } + // 将平台字节序转换为小端字节序 + std::string platform_2_little_endian(void* t, int size) { + char* memory = reinterpret_cast(t); + std::string result(memory, memory + size); + // 如果当前平台是大端,反转字符串,使其成为小端字节序 + if (is_big_endian) { + std::reverse(result.begin(), result.end()); + } + return result; + } +#define GET_BIT(byte, pos) (((byte) >> (pos)) & 1) +#define SET_BIT(byte, pos) ((byte) |= (1 << (pos))) +#define CLR_BIT(byte, pos) ((byte) &= ~(1 << (pos))) +#define WRITE_BIT(byte, pos, val) \ + ((byte) = ((byte) & ~(1 << (pos))) | (((val) & 1) << (pos))) + template + void bit_op(void* dest, size_t d_offset, const void* src, size_t s_offset, size_t num, BitOp op) { + auto* d = (uint8_t*)dest; + const auto* s = (const uint8_t*)src; + for (size_t i = 0; i < num; ++i) { + size_t src_pos = s_offset + i; + size_t dst_pos = d_offset + i; + uint8_t src_bit = GET_BIT(s[src_pos / 8], src_pos % 8); + uint8_t dst_bit = GET_BIT(d[dst_pos / 8], dst_pos % 8); + uint8_t res_bit = op(dst_bit, src_bit); + WRITE_BIT(d[dst_pos / 8], dst_pos % 8, res_bit); + } + } + void bit_move(void* d, size_t doff, const void* s, size_t soff, size_t n) { + bit_op(d, doff, s, soff, n, [](uint8_t, uint8_t s) { return s; }); + } + void bit_xor(void* d, size_t doff, const void* s, size_t soff, size_t n) { + bit_op(d, doff, s, soff, n, [](uint8_t d, uint8_t s) { return d ^ s; }); + } + void bit_or(void* d, size_t doff, const void* s, size_t soff, size_t n) { + bit_op(d, doff, s, soff, n, [](uint8_t d, uint8_t s) { return d | s; }); + } + void bit_and(void* d, size_t doff, const void* s, size_t soff, size_t n) { + bit_op(d, doff, s, soff, n, [](uint8_t d, uint8_t s) { return d & s; }); + } +} +constexpr uint8_t reverse8(uint8_t b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} +constexpr std::array make_reverse8_table() { + std::array arr = {}; + for (uint16_t i = 0; i < 256; ++i) arr[i] = reverse8(static_cast(i)); + return arr; +} +constexpr auto reserve8 = make_reverse8_table(); +#define BYTE(b) reserve8[0b##b] +#ifdef _USE_GTEST +#include +using namespace yC; +// 辅助函数:字节数组转十六进制字符串(方便调试) +std::string bytes_to_hex(const std::vector& v) { + std::ostringstream oss; + for (auto b : v) { + oss << std::hex << std::setw(2) << std::setfill('0') << (int)b; + } + return oss.str(); +} +// case 2:dest 偏移,src 对齐 +TEST(BitMoveTest, DestBitOffset) { + struct Data { + std::vector dest; + size_t dest_offset; + std::vector src; + size_t src_offset; + size_t num; + std::vector expected_move; + std::vector expected_xor; + std::vector expected_or; + std::vector expected_and; + std::string msg; + }; + std::vector list; + // 1. 目标偏移2位,拷贝6位 + // 00 00000000 + // dest 000000 + // src 111100 + // xor: 111100 + // or: 111100 + // and: 000000 + Data d1{ + {BYTE(00000000), BYTE(00000000)}, + 2, + {BYTE(11110000), BYTE(00001111)}, + 0, + 6, + {BYTE(00111100), BYTE(00000000)}, + // move + {BYTE(00111100), BYTE(00000000)}, + // xor (0x3C, 0x00) + {BYTE(00111100), BYTE(00000000)}, + // or (0x3C, 0x00) + {BYTE(00000000), BYTE(00000000)}, + // and (0x00, 0x00) + "目标偏移2位,拷贝6位" + }; + + char arr[100]; + size_t a = get_mem2hex_size(2, 0); + mem2hex_Ex2(arr, d1.expected_move.data(), 2, false, ""); + + + + EXPECT_STREQ(hex2bin(std::string(arr, a)).c_str(), "0011110000000000"); + + + // 2. 目标偏移1位,拷贝9位,跨越目标字节 + // 1 000001 + // dest 000000000 + // src 011111111 + // xor: 011111111 + // or: 011111111 + // and: 000000000 + list.push_back({ + {BYTE(10000000), BYTE(00000001)}, + 1, + {BYTE(01111111), BYTE(10000000)}, + 0, + 9, + {BYTE(10111111), BYTE(11000001)}, + // move + {BYTE(10111111), BYTE(11000001)}, + // xor (0xFE, 0x83) + {BYTE(10111111), BYTE(11000001)}, + // or (0xFE, 0x83) + {BYTE(10000000), BYTE(00000001)}, + // and (0x01, 0x03) + "目标偏移1位,拷贝9位,跨字节" + }); + // 3. 目标偏移4,源偏移3,拷贝8位 + // 1100 1010 + // dest 00111010 + // src 10000000 + // xor 10111010 + // or 10111010 + // and 00000000 + list.push_back({ + {BYTE(11000011), BYTE(10101010)}, + 4, + {BYTE(11110000), BYTE(00001111)}, + 3, + 8, + {BYTE(11001000), BYTE(00001010)}, + // move + {BYTE(11001011), BYTE(10101010)}, + // xor + {BYTE(11001011), BYTE(10101010)}, + // or + {BYTE(11000000), BYTE(00001010)}, + // and + "目标偏移4,源偏移3,拷贝8位" + }); + // 4. 全1拷贝部分,全0目标 + // 00000 0000 + // dest 0000000 + // src 1111111 + // xor 1111111 + // or 1111111 + // and 0000000 + list.push_back({ + {BYTE(00000000), BYTE(00000000)}, + 5, + {BYTE(11111111), BYTE(11111111)}, + 2, + 7, + {BYTE(00000111), BYTE(11110000)}, + // move + {BYTE(00000111), BYTE(11110000)}, + // xor + {BYTE(00000111), BYTE(11110000)}, + // or + {BYTE(00000000), BYTE(00000000)}, + // and + "全1源,拷贝7位到目标末尾" + }); + // 5. 源目标均有内容(交错) + // 110 10011 + // dest 01100001 + // src 10100101 + // xor 11000100 + // or 11100101 + // and 00100001 + list.push_back({ + {BYTE(11001100), BYTE(00110011)}, + 3, + {BYTE(10101010), BYTE(01010101)}, + 4, + 8, + {BYTE(11010100), BYTE(10110011)}, + // move + {BYTE(11011000), BYTE(10010011)}, + // xor + {BYTE(11011100), BYTE(10110011)}, + // or + {BYTE(11000100), BYTE(00110011)}, + // and + "目标有内容,源偏移4,拷贝8位" + }); + // 6. 源目标完全重叠 + // 1011 01 + // dest 1001110100 + // src 1101111010 + // move 1101111010 + // xor 0100001110 + // or 1101111110 + // and 1001110000 + list.push_back({ + {BYTE(10111001), BYTE(11010001)}, + 4, + {BYTE(10110111), BYTE(10100101)}, + 2, + 10, + {BYTE(10111101), BYTE(11101001)}, + // move + {BYTE(10110100), BYTE(00111001)}, + // xor + {BYTE(10111101), BYTE(11111001)}, + // or + {BYTE(10111001), BYTE(11000001)}, + // and + "目标/源重叠,偏移不同" + }); + for (auto& d : list) { + auto dest_move = d.dest; + auto dest_xor = d.dest; + auto dest_or = d.dest; + auto dest_and = d.dest; + bit_move(dest_move.data(), d.dest_offset, d.src.data(), d.src_offset, d.num); + bit_xor(dest_xor.data(), d.dest_offset, d.src.data(), d.src_offset, d.num); + bit_or(dest_or.data(), d.dest_offset, d.src.data(), d.src_offset, d.num); + bit_and(dest_and.data(), d.dest_offset, d.src.data(), d.src_offset, d.num); + EXPECT_EQ(dest_move, d.expected_move) << "move 结果: " << hex2bin(bytes_to_hex(dest_move)) << "\n期望: " << + hex2bin(bytes_to_hex(d.expected_move)) << "\n" + d.msg; + EXPECT_EQ(dest_xor, d.expected_xor) << "xor 结果: " << bytes_to_hex(dest_xor) << "\n期望: " << + bytes_to_hex(d.expected_xor) << "\n" + d.msg; + EXPECT_EQ(dest_or, d.expected_or) << "or 结果: " << bytes_to_hex(dest_or) << "\n期望: " << + bytes_to_hex(d.expected_or) << "\n" + d.msg; + EXPECT_EQ(dest_and, d.expected_and) << "and 结果: " << bytes_to_hex(dest_and) << "\n期望: " << + bytes_to_hex(d.expected_and) << "\n" + d.msg; + } + +} + + +TEST(Bit222, WriteAndReadFullByte223) { + + + + + std::pmr::string s("11101110"); + std::string s2("11101110"); + std::cout << bin2hex(s) << std::endl; + std::cout << bin2hex(s2) << std::endl; + std::cout << bin2hex("11101110") << std::endl; + + + std::cout << "111:" << hex2bin("01") << std::endl; + std::cout << "big_dian:" << bin4_to_hex(true, "0011") << std::endl; + std::cout << "small_dian:" << bin4_to_hex(false, "0011") << std::endl; +} + +TEST(BitOpsLE, WriteAndReadFullByte) { + uint8_t buf[8] = {0}; + set_bits(buf, 0, 8, 0xAB); // 小端模式:低位在低bit + EXPECT_EQ(buf[0], 0xAB); + EXPECT_EQ(get_bits(buf, 0, 8), 0xAB); +} +TEST(BitOpsLE, CrossByteWriteAndRead) { + uint8_t buf[8] = {0}; + set_bits(buf, 4, 12, 0xF0F); // 写12位,偏移4 + EXPECT_EQ(get_bits(buf, 4, 12), 0xF0F); +} +TEST(BitOpsLE, UnalignedBitWriteAndRead) { + uint8_t buf[8] = {0}; + set_bits(buf, 5, 10, 0x2AA); // 偏移5位,写10位 + EXPECT_EQ(get_bits(buf, 5, 10), 0x2AA); +} +TEST(BitOpsLE, SingleBitWriteAndRead) { + uint8_t buf[8] = {0}; + set_bits(buf, 10, 1, 1); + EXPECT_EQ(get_bits(buf, 10, 1), 1); + set_bits(buf, 10, 1, 0); + EXPECT_EQ(get_bits(buf, 10, 1), 0); +} +TEST(BitOpsLE, WriteAndReadFullUint64) { + uint8_t buf[8] = {0}; + set_bits(buf, 0, 64, 0x123456789ABCDEF0ULL); + EXPECT_EQ(get_bits(buf, 0, 64), 0x123456789ABCDEF0ULL); +} +TEST(BitOpsLE, OverwriteZeroBits) { + uint8_t buf[8]; + std::memset(buf, 0xFF, sizeof(buf)); + set_bits(buf, 16, 16, 0x0000); + EXPECT_EQ(get_bits(buf, 16, 16), 0x0000); + // 其它位应保持原值 + for (size_t i = 0; i < 2; ++i) + EXPECT_EQ(buf[i], 0xFF); + for (size_t i = 4; i < 8; ++i) + EXPECT_EQ(buf[i], 0xFF); +} +TEST(BitOpsLE, AllZeroAndAllOne) { + uint8_t buf[8] = {0}; + set_bits(buf, 0, 64, 0xFFFFFFFFFFFFFFFFULL); + EXPECT_EQ(get_bits(buf, 0, 64), 0xFFFFFFFFFFFFFFFFULL); + set_bits(buf, 0, 64, 0x0ULL); + EXPECT_EQ(get_bits(buf, 0, 64), 0x0ULL); +} +#else +#endif diff --git a/module/dll_source_lzy/114/out/lib/hulc/Bit.h b/module/dll_source_lzy/114/out/lib/hulc/Bit.h new file mode 100644 index 0000000..59e8c58 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/Bit.h @@ -0,0 +1,309 @@ +#ifndef BitSet_H +#define BitSet_H +#include +#ifdef __linux__ +#include +#endif +#include +#include +#include +#include + +namespace yC { + template + typename std::enable_if::value, T>::type + create_string(size_t size, std::pmr::memory_resource* resource = nullptr) { + std::pmr::memory_resource* r = resource ? resource : std::pmr::get_default_resource(); + std::pmr::string ret(r); + ret.resize(size); + return ret; // 返回 std::pmr::string + } + + template + typename std::enable_if::value, T>::type + create_string(size_t size, std::pmr::memory_resource* resource = nullptr) { + std::string ret; + ret.resize(size); + return ret; // 返回 std::string + } + + template + struct cacl_type { + using type = T; + size_t size(T& a) { + return a.size(); + } + static void* data(T& a) { + return (void*)a.data(); + } + static size_t size(const T& a) { + return a.size(); + } + static void* data(const T& a) { + return (void*)a.data(); + } + }; + template <> + struct cacl_type { + using type = std::string; + static size_t size(const char* a) {return strlen(a);} + static void* data(const char* a) {return (void*)a;} + }; + + template + struct cacl_type { + using type = std::string; + static size_t size(const char (&a)[N]) { return N - 1; } + static void* data(const char (&a)[N]) { return (void*)a; } + }; + + template + struct cacl_type { + using type = std::string; + static size_t size(const char (&a)[N]) { return N - 1; } + static void* data(const char (&a)[N]) { return (void*)a; } + }; +} + + + +#define Base template , \ +typename STR_Type = std::basic_string, allocator_type>, \ +typename ret_type = typename cacl_type::type \ +> +// #define Base template <\ +// typename STR_Type, \ +// typename ret_type = typename cacl_type::type \ +// > + + +namespace yC { + // 检查当前机器是否是大端字节序 + extern const bool is_big_endian; + void big_endian_2_platform(void* dest, const char* d, size_t size); + void little_endian_2_platform(void* dest, const char* d, size_t size); + + + + void big_endian_2_platform(const std::string& big_endian, char* dest); + void little_endian_2_platform(const std::string& little_endian, char* dest); + + + std::string big_endian_2_platform(const std::string& big_endian); + std::string little_endian_2_platform(const std::string& little_endian); + std::string platform_2_big_endian(void* memory, int size); + std::string platform_2_little_endian(void* memory, int size); + + + + + inline size_t get_xor_hex_size(size_t n1, size_t n2){return std::max(n1, n2);} + void xor_hex_Ex(void* dest, const char* s1, size_t n1, const char* s2, size_t n2, bool big_dian, bool is_upper); + Base + ret_type xorHex(const STR_Type& s1, const STR_Type& s2, std::pmr::memory_resource* resource = nullptr) { + auto n1 = cacl_type::size(s1); + auto n2 = cacl_type::size(s2); + auto size = get_xor_hex_size(n1, n2); + auto ret = create_string(size, resource); + xor_hex_Ex((void*)ret.data(), (const char*)cacl_type::data(s1), n1, (const char*)cacl_type::data(s2), n2, true, true); + return ret; + } + + + inline size_t get_xor_bin_size(size_t n1, size_t n2){return std::max(n1, n2);} + void xor_bin_Ex(void* dest, const char* s1, size_t n1, const char* s2, size_t n2, bool big_dian); + Base + ret_type xorBin(const STR_Type& s1, const STR_Type& s2, std::pmr::memory_resource* resource = nullptr) { + auto n1 = cacl_type::size(s1); + auto n2 = cacl_type::size(s2); + auto size = get_xor_bin_size(n1, n2); + auto ret = create_string(size, resource); + xor_bin_Ex((void*)ret.data(), (const char*)cacl_type::data(s1), n1, (const char*)cacl_type::data(s2), n2, true); + return ret; + } + + + + + inline size_t get_bin2hex_size(size_t num) {return (num + 3) / 4;} + void bin2hex_Ex(void* dest, const char* data, size_t size, bool big_dian, bool isupper); + Base + ret_type bin2hex(const STR_Type& mem, std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_bin2hex_size(size), resource); + bin2hex_Ex((void*)ret.data(), (const char*)cacl_type::data(mem), size, true, true); + return ret; + } + + + + inline size_t get_hex2bin_size(size_t num) {return num * 4;} + void hex2bin_Ex(void* dest, const char* data, size_t size, bool big_dian); + Base + ret_type hex2bin(const STR_Type& mem, std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_hex2bin_size(size), resource); + hex2bin_Ex((void*)ret.data(), (const char*)cacl_type::data(mem), size, true); + return ret; + } + + inline size_t get_bin2mem_size(size_t num) {return num/8;}; + void bin2mem(void* dest, const char* data, size_t size); + Base + STR_Type bin2mem(const STR_Type& mem, std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_bin2mem_size(size), resource); + bin2mem((void*)ret.data(), (const char*)cacl_type::data(mem), size); + return ret; + } + + // inline ret_str bin2mem(param_str mem, std::pmr::memory_resource* resource = nullptr) { + // auto size = mem.size(); + // auto ret = ret_str(); + // ret.resize(get_bin2mem_size(size)); + // bin2mem((void*)ret.data(), (const char*)mem.data(), size); + // return ret; + // } + + + inline size_t get_hex2mem_size(size_t hex_hum) {return hex_hum/2;}; + void hex2mem_Ex(void* dest, const char* data, size_t size); + Base + STR_Type hex2mem(const STR_Type& mem, std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_hex2mem_size(size), resource); + hex2mem_Ex((void*)ret.data(), (const char*)cacl_type::data(mem), size); + return ret; + } + + // inline ret_str hex2mem(param_str hex, std::pmr::memory_resource* resource = nullptr) { + // auto size = hex.size(); + // auto ret = ret_str(); + // ret.resize(get_hex2mem_size(size)); + // hex2mem_Ex((void*)ret.data(), (const char*)hex.data(), size); + // return ret; + // } + + + inline size_t get_mem2hex_size(size_t num, size_t middle_len) { return num * 2 + (num - 1) * middle_len;} + void mem2hex_Ex2(void* dest, void* mem, size_t num, bool uppercase, const char* middle, size_t middle_len = std::numeric_limits::max()); + Base + STR_Type mem2hex(const STR_Type& mem, bool uppercase = true, const std::string& middle = "", std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_mem2hex_size(size, middle.size()), resource); + mem2hex_Ex2(ret.data(), cacl_type::data(mem), size, uppercase, middle.data(), middle.size()); + return ret; + } + + // inline ret_str mem2hex(param_str& mem, bool uppercase = true, const std::string& middle = "", std::pmr::memory_resource* resource = nullptr) { + // auto size = get_mem2hex_size(mem.size(), middle.size()); + // auto ret = ret_str(); + // mem2hex_Ex2(ret.data(), (void*)mem.data(), size, uppercase, middle.data(), middle.size()); + // return ret; + // } + + + template + void get_bin(std::string& msg, int& n) { + n = std::bitset(msg.substr(start, len)).to_ullong(); + } + template + void get_bin(std::string& msg, Enum& n) { + n = static_cast(std::bitset(msg.substr(start, len)).to_ullong()); + } + template + Enum get_bin(std::string& msg) { + return static_cast(std::bitset(msg.substr(start, len)).to_ullong()); + } + template + int get_bin(const std::string& msg) { + return std::bitset(msg.substr(start, len)).to_ullong(); + } + template + void set_bin(std::string& msg, unsigned long long n) { + msg.replace(start, len, std::bitset(n).to_string()); + } + template + void set_bin(std::string& msg, const std::string& value) { + if (value.size() != len) { + std::cout << "len == " << len << " bits.size() == " << value.size() << std::endl; + throw std::out_of_range("Start position and length exceed bit string size"); + } + msg.replace(start, len, value); + } + template + std::string to_bin(unsigned long long n) { + return std::bitset(n).to_string(); + } + template + std::string to_bin(double n) { + return std::bitset(static_cast(n)).to_string(); + } + template + T bin2(const std::string& binstr) { + return std::stoull(binstr, nullptr, 2); + } + template + unsigned long long hex2(const std::string& hexstr) { + return std::stoull(hexstr, nullptr, 16); + } + template <> + inline int bin2(const std::string& binstr) { + int bit_limit = sizeof(int) * 8; + if (binstr.size() > bit_limit) { + std::cout << "Bitset.h inline int bin2 error! Binary string exceeds int bit limit" << std::endl; + } + return std::stoi(binstr, nullptr, 2); + } + // 设置dest的从off位起n位,写入value的低n位 + template + void set_bits(void* dest, size_t off, size_t n, T value) { + auto d = static_cast(dest); + size_t bit_pos = off; + size_t val_pos = 0; + while (n > 0) { + size_t byte_idx = bit_pos / 8; + size_t bit_in_byte = bit_pos % 8; + size_t bits_in_this_byte = std::min(n, 8 - bit_in_byte); + // 为当前字节构建掩码 + uint8_t mask = ((1u << bits_in_this_byte) - 1) << bit_in_byte; + // 取value对应的低bits_in_this_byte位 + uint8_t v = (value >> val_pos) & ((1u << bits_in_this_byte) - 1); + // 清除目标字节对应位置后设置 + d[byte_idx] = (d[byte_idx] & ~mask) | ((v << bit_in_byte) & mask); + bit_pos += bits_in_this_byte; + val_pos += bits_in_this_byte; + n -= bits_in_this_byte; + } + } + + + + // 大批量位处理性能不好 但是通常也没有这样的场景 + void bit_move(void* d, size_t doff, const void* s, size_t soff, size_t n); + void bit_xor(void* d, size_t doff, const void* s, size_t soff, size_t n); + void bit_or(void* d, size_t doff, const void* s, size_t soff, size_t n); + void bit_and(void* d, size_t doff, const void* s, size_t soff, size_t n); + template + T get_bits(void* src, size_t off, size_t n) { + auto s = static_cast(src); + size_t bit_pos = off; + size_t val_pos = 0; + T result = 0; + while (n > 0) { + size_t byte_idx = bit_pos / 8; + size_t bit_in_byte = bit_pos % 8; + size_t bits_in_this_byte = std::min(n, 8 - bit_in_byte); + uint8_t mask = ((1u << bits_in_this_byte) - 1) << bit_in_byte; + uint8_t bits = (s[byte_idx] & mask) >> bit_in_byte; + result |= (T(bits) << val_pos); + bit_pos += bits_in_this_byte; + val_pos += bits_in_this_byte; + n -= bits_in_this_byte; + } + return result; + } +} + +#undef Base +#endif diff --git a/module/dll_source_lzy/114/out/lib/hulc/DataOutputFormats.cpp b/module/dll_source_lzy/114/out/lib/hulc/DataOutputFormats.cpp new file mode 100644 index 0000000..33c3fa0 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/DataOutputFormats.cpp @@ -0,0 +1,106 @@ +#include "DataOutputFormats.h" + +#include +#include + +using namespace yC; + +MLAT_timestamp::MLAT_timestamp() { + std::chrono::time_point t = std::chrono::high_resolution_clock::now(); + // 获取当前的系统时间(秒) + std::chrono::duration duration_since_epoch = t.time_since_epoch(); + // 获取秒数和纳秒数 + auto seconds = std::chrono::duration_cast(duration_since_epoch); + auto nanoseconds = std::chrono::duration_cast(duration_since_epoch); + // 获取当天的秒数和纳秒数 + this->daysec = seconds.count() % 86400; // 86400 = 24 * 60 * 60 (一天的秒数) + this->nanosec = nanoseconds.count() % 86400000000000; // 1秒 = 10^9纳秒 + memory = bin2mem(to_bin_string()); + parse_hhmmss(); +} + + +MLAT_timestamp::MLAT_timestamp(const std::string& memory_6) : memory(memory_6) { + assert(memory_6.size() == 6); + auto msg_bin = hex2bin(mem2hex(memory)); + daysec = std::bitset<18>(msg_bin.substr(0, 18)).to_ulong(); + nanosec = std::bitset<30>(msg_bin.substr(18, 30)).to_ulong(); + parse_hhmmss(); + + //assert(memory_6 == bin2mem(to_bin_string()), "MLAT_timestamp,内存转换错误!"); +} + +MLAT_timestamp::MLAT_timestamp(std::uint32_t daysec, std::uint32_t nanosec) : daysec(daysec), nanosec(nanosec) { + parse_hhmmss(); +} + + +std::string MLAT_timestamp::to_bin_string() const { + std::bitset<18> daysec_bits = daysec; + std::bitset<30> nanosec_bits = nanosec; + return daysec_bits.to_string() + nanosec_bits.to_string(); +} + +// std::string MLAT_timestamp::to_memory() { +// return bin2memory(to_bin_string()); +// } + +std::string MLAT_timestamp::to_memory() const { + return memory; +} + +void MLAT_timestamp::parse_hhmmss() { + hh = (daysec/3600) % 24; + mm = (daysec/60) % 60; + ss = daysec % 60; +} +std::string packet_to_escape_format(std::string t) { + std::string result = "\x1a"; + for (size_t i = 1; i < t.size(); ++i) { + result.push_back(t[i]); // 插入当前字符 + if (t[i] == '\x1a') { + result.push_back('\x1a'); // 如果是 0x1a,插入另一个 0x1a + } + } + return result; +} +void Binary_Format_handle_buffer(std::string& buffer, const std::string& data, const std::function& callback) { + size_t i = 0; + while (i < data.size()) { + if (data[i] == 0x1A) { + // 如果遇到起始标记 0x1A + if (i + 1 < data.size() && data[i + 1] == 0x1A) { + // 如果是转义的 0x1A 0x1A,继续处理 + buffer.push_back(0x1A); // 添加一个正常的 0x1A 字节 + i += 2; // 跳过这两个字节 + } else { + // 检测到起始标记 0x1A,认为是新消息的开始 + if (!buffer.empty()) { + // 如果当前消息 buffer 中已有数据,调用回调函数处理当前消息 + callback(buffer); + buffer.clear(); // 清空当前消息的缓存 + } + buffer.push_back(0x1A); + // 跳过当前的 SOP 标记 (0x1A) + i++; + } + } else + { + // 如果不是 0x1A,直接将数据添加到 buffer 中 + buffer.push_back(data[i]); + i++; + } + } +} + + + +// 9 + 2 +// 9 + 7 +// 9 + 14 +std::string mode_s_msg_packet_to_server_packet(std::uint32_t id, std::string packet) { + std::string id_code = yC::platform_2_big_endian((char*)&id, sizeof(id)); + //assert(id_code.size() == 4, "id.size != 4"); + packet.insert(1, id_code.c_str(), 4); + return packet; +} \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/hulc/DataOutputFormats.h b/module/dll_source_lzy/114/out/lib/hulc/DataOutputFormats.h new file mode 100644 index 0000000..aab359e --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/DataOutputFormats.h @@ -0,0 +1,531 @@ +#ifndef DATAOUTPUTFORMATS_H +#define DATAOUTPUTFORMATS_H +#include "base_format.h" +using namespace yC; + +// AVR format +// *02E99619FACDAE; +// *8D3C5EE69901BD9540078D37335F; +// *7700; +inline std::string create_AVR_format(const std::string& msg_hex) { + return "*" + msg_hex + ";"; +} + +// @016CE3671C7423FFE7AB7BFCAB; +// @016CE3671AA8A800199A8BB80030A8000628F400; +// @016CE3671C747700; + +// @5500068BA46CB402c18d34fc8000; +inline std::string create_MLAT_AVR_format(const std::string& msg_hex, MLAT_timestamp* timestamp) { + //std::string signal_level_str = bin2hex(std::bitset<8>(signal_level).to_string()); + //std::cout << "signal:" << signal_level_str << std::endl; + return "@" + bin2hex(timestamp->to_bin_string()) + msg_hex + ";"; +} + + +// Binary Format +// "1" : 6 byte MLAT timestamp, 1 byte signal level, 2 byte Mode-AC +// "2" : 6 byte MLAT timestamp, 1 byte signal level, 7 byte Mode-S short frame +// "3" : 6 byte MLAT timestamp, 1 byte signal level, 14 byte Mode-S long frame +// "4" : 6 byte MLAT timestamp, 1 byte unused, DIP switch configuration settings, time stamp error ticks as int8_t (1 tick is 15ns) (message "4" not on Mode-S Beast classic) +// : true 0x1a +// is 0x1a, and "1", "2" and "3" are 0x31, 0x32 and 0x33 +inline std::string create_Binary_Format_memory(const std::string& msg_hex, char signal_level, MLAT_timestamp* timestamp) { + std::string type; + switch (msg_hex.size()) { + case 4: type = '1'; + break; + case 14: type = '2'; + break; + case 28: type = '3'; + break; + default: std::cerr << msg_hex << "create_Binary_Format error is not a valid binary format." << std::endl; + throw std::runtime_error("create_Binary_Format error!"); + } + + + std::string t = timestamp->to_memory() + signal_level + yC::hex2mem(msg_hex); + + // std::cout << "msg_hex.size():" << msg_hex.size() << std::endl; + // std::cout << "hex2memory(msg_hex):" << hex2memory(msg_hex).size() << std::endl; + // std::cout << t.size() << std::endl; + std::string result; + + // 遍历原始字符串 + for (size_t i = 0; i < t.size(); ++i) { + result.push_back(t[i]); // 插入当前字符 + if (t[i] == '\x1a') { + result.push_back('\x1a'); // 如果是 0x1a,插入另一个 0x1a + } + } + + return char(0x1a) + type + result; +} + +// 把要传递的信息转化成转义字符 二进制==>转义二进制 +std::string packet_to_escape_format(std::string t); + +inline std::string unescape_packet(std::string t) { + std::string result; + for (size_t i = 0; i < t.size(); ++i) { + if (t[i] == '\x1a' && i + 1 < t.size() && t[i + 1] == '\x1a') { + result.push_back('\x1a'); // 如果是连续的两个 \x1a,只添加一个 + ++i; // 跳过下一个 \x1a + } else { + result.push_back(t[i]); // 否则,直接添加当前字符 + } + } + return result; +} + + +// 用于dip_setting +// FPGA switch settings +// Bit 0: Data output mode(port 10002 only) Binary AVR hexdump +// Bit 1: Raw data frame types DF11, DF17 and DF18 only no pre-filtering +// Bit 2: Timestamps within AVR format enabled disabled +// Bit 3: not used +// Bit 4: Timestamp mode GPS legacy 12MHz +// Bit 5: FPGA/USB handshake disabled enabled +// Bit 6: 1 bit forward error correction (FEC) disabled enabled +// Bit 7: Mode-A/C decoding enabled disabled +// GPS status information +// Bit 0: Antenna status good fail +// Bit 1: Tracking at least 1 satellite true false +// Bit 2: Tracking at least 3 satellites true false +// Bit 3: Time value base UTC time GPS time (8sec offset) +// Bit 4: unused +// Bit 5: unused +// Bit 6: unused +// Bit 7: Daysec value range 0 ... 86399 1 ... 86400 +inline std::string create_Binary_Format_4(MLAT_timestamp timestamp, char signal_level, char dip_setting, char time_stamp_error_ticks) { + return char(0x1a) + "4" + timestamp.to_bin_string() + char(0xaa) + dip_setting + time_stamp_error_ticks; +} + +//相对时间戳(无 GPS):在没有连接 GPS 接收器的情况下,时间戳是一个 48 位无符号数字, +//计数 12 MHz 周期。时间戳大约每 271 天换行一次。 +struct TCXO_Clock { + unsigned long long times; + + explicit TCXO_Clock(const std::string& hex) { + times = std::bitset<48>(hex2bin(hex)).to_ullong(); + } + + [[nodiscard]] std::string to_string() const { + // 每个周期的时间是 1 / 12,000,000 秒 + double seconds = static_cast(times) / 12000000.0; + // 计算小时、分钟、秒 + int hours = static_cast(seconds / 3600); + seconds -= hours * 3600; + int minutes = static_cast(seconds / 60); + seconds -= minutes * 60; + int secs = static_cast(seconds); + // 格式化为时分秒字符串 + std::ostringstream oss; + oss.fill('0'); + oss << hours << ":"; + oss.width(2); + oss << minutes << ":"; + oss.width(2); + oss << secs; + return oss.str(); + } +}; + +/* +Periodic status message which is emitted every second +0x1A 0x48 0x01 0x18 SerNum Flags I.U. xTime Lat Lon Alt Sat HDOP +Fields: + SerNum 32-bit(1) Device Serial Number + Flags 16-bit(1) Status Flags* + I.U. 16-bit Internal Use + xTime 32-bit(1) Unix-Timestamp (Seconds since midnight 1970-01-01, UTC) + Lat 32-bit(1) Latitude as 32-bit BAM 2, thus 232 ≡ 360° + Lon 32-bit(1) Latitude as 32-bit BAM 2 + Alt 16-bit(1) Signed altitude meters + Sat 8-bit Number of satellites used in fix + HDOP 8-bit HDOP * 10, thus a value of 12 is HDOP 1.2 +1) MSB-first +2) https://en.wikipedia.org/wiki/Binary_scaling#Binary_angles + Status Flags: + 1xxx xxxx xxxx xxxx - GPS device detected -> Using absolute timestamp + x1xx xxxx xxxx xxxx - GPS valid (GGA, RMC present) + xx1x xxxx xxxx xxxx - GPS currently has a valid fix + xxx1 xxxx xxxx xxxx - High accuracy absolute time is available via PPS + xxxx xxxx 1xxx xxxx - TX-Queue overflow since start-up (normal if HW-handshaking is used) + xxxx xxxx x1xx xxxx - TX-Queue overflow during last second (probably due to HW-handshaking) + xxxx xxxx xx1x xxxx - Excessive NMEA found (other than GGA/RMC) +*/ +// +// All HULC messages (Type 0x48) have the following structure: +// 0x1A 0x48 ID LEN +// Fields: +// ID: HULC Message ID +// LEN: Number of bytes following +struct HULC_Message { + std::uint8_t head; + std::uint8_t id; + std::uint8_t len; // 指示下面的长度 +}; + + + + +struct HULC_Status_Message : HULC_Message { + std::uint32_t SerNum{}; + std::uint16_t Flags{}; // MSB大端字节序 + std::uint16_t I_U_{}; // Internal Use + std::uint32_t xTime{}; // Unix-Timestamp (Seconds since midnight 1970-01-01, UTC) + std::int32_t Lat{}; + std::int32_t Lon{}; + std::int16_t Alt{}; + std::uint8_t Sat{}; // Sat字段记录了定位过程中使用的卫星数量 + std::uint8_t HDOP{}; + + [[nodiscard]] double get_HDOP() const { + // HDOP(Horizontal Dilution of Precision) 描述的是 水平精度的衰减,即 GPS 系统的水平定位精度与卫星的几何布局有关。HDOP 数值越低,说明卫星的几何布局越好,定位精度越高。 + // 较低的 HDOP 值(例如 1-2)表示卫星信号较强,且卫星的分布较为理想,因此定位精度较高。 + // 较高的 HDOP 值(例如 5 或更高)表示卫星信号较弱,或卫星的分布较差,可能导致较差的定位精度。 + return double(HDOP) / 10.0; + } + + void set_latitude(double latitude) { + // 根据经纬度设置 Lat 值 + Lat = static_cast((latitude / 360.0) * pow(2, 32)); + } + + void set_longitude(double longitude) { + // 根据经纬度设置 Lon 值 + Lon = static_cast((longitude / 360.0) * pow(2, 32)); + } + + [[nodiscard]] double get_latitude() const { + return (static_cast(Lat) / pow(2, 32)) * 360.0; + } + + [[nodiscard]] double get_longitude() const { + return (static_cast(Lon) / pow(2, 32)) * 360.0; + } + + [[nodiscard]] std::string get_latitude_d_m_s() const{ + return decimalToDMS(get_latitude()); + } + + [[nodiscard]] std::string get_longitude_d_m_s() const{ + return decimalToDMS(get_longitude()); + } + + [[nodiscard]] std::string get_timestamp_string() const{ + return convert_timestamp_to_string(xTime); + } + + static std::string convert_timestamp_to_string(std::uint32_t time32) { + std::time_t time = time32; + std::stringstream ss; + ss << std::put_time(std::gmtime(&time), "%Y-%m-%d %H:%M:%S"); // 使用 std::put_time 格式化日期 + return ss.str( + + ); + // 返回格式化后的字符串 + // std::time_t time = time32; + // std::stringstream ss; + // // 将时间戳转换为本地时间 + // ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S"); // 使用 std::localtime 格式化日期 + // return ss.str(); // 返回格式化后的字符串 + // // Unix Timestamp + // std::time_t timestamp = xTime; + // + // std::tm* ptm = std::gmtime(×tamp); + // + // return std::asctime(ptm); + } + + + // 转化经纬度变成 度 分 秒 + static std::string decimalToDMS(double decimalDegree) { + int degrees = static_cast(decimalDegree); + double minutesDouble = (decimalDegree - degrees) * 60; + int minutes = static_cast(minutesDouble); + int seconds = static_cast((minutesDouble - minutes) * 60.0); + return "(" + std::to_string(degrees) + "°" + std::to_string(minutes) + "'" + std::to_string(seconds) + "\")"; + } + /* + Status Flags: + 1xxx xxxx xxxx xxxx - GPS device detected -> Using absolute timestamp + x1xx xxxx xxxx xxxx - GPS valid (GGA, RMC present) + xx1x xxxx xxxx xxxx - GPS currently has a valid fix + xxx1 xxxx xxxx xxxx - High accuracy absolute time is available via PPS + xxxx xxxx 1xxx xxxx - TX-Queue overflow since start-up (normal if HW-handshaking is used) + xxxx xxxx x1xx xxxx - TX-Queue overflow during last second (probably due to HW-handshaking) + xxxx xxxx xx1x xxxx - Excessive NMEA found (other than GGA/RMC) + */ + [[nodiscard]] bool GPS_device_detected() const { + return Flags & (1 << 15); // 检查第 15 位,GPS 设备是否检测到 + } + + [[nodiscard]] bool GPS_valid() const { + return Flags & (1 << 14); // 检查第 14 位,GPS 数据是否有效 + } + + [[nodiscard]] bool GPS_has_valid_fix() const { + return Flags & (1 << 13); // 检查第 13 位,GPS 是否有有效定位 + } + + [[nodiscard]] bool GPS_high_accuracy_time() const { + return Flags & (1 << 12); // 检查第 12 位,是否通过 PPS 提供高精度绝对时间 + } + + [[nodiscard]] bool TX_queue_overflow_since_startup() const { + return Flags & (1 << 7); // 检查第 7 位,自启动以来是否发生 TX 队列溢出 + } + + [[nodiscard]] bool TX_queue_overflow_last_second() const { + return Flags & (1 << 6); // 检查第 6 位,过去一秒内是否发生 TX 队列溢出 + } + + [[nodiscard]] bool excessive_NMEA_found() const { + return Flags & (1 << 5); // 检查第 5 位,是否发现过多的 NMEA 数据(非 GGA/RMC) + } + + [[nodiscard]] Json toJson() const { + Json ret = Json::object(); + ret.append({"序列号", SerNum}); + ret.append({"状态标志", Flags}); + ret.append({"内部使用", I_U_}); + ret.append({"时间戳", get_timestamp_string()}); + ret.append({"纬度", std::to_string(get_latitude()) + " " + get_latitude_d_m_s()}); + ret.append({"经度", std::to_string(get_longitude()) + " " + get_longitude_d_m_s()}); + ret.append({"高度", Alt}); + ret.append({"卫星数量", Sat}); + ret.append({"HDOP", get_HDOP()}); + ret.append({"GPS设备检测到", GPS_device_detected()}); + ret.append({"GPS有效", GPS_valid()}); + ret.append({"GPS有效定位", GPS_has_valid_fix()}); + ret.append({"GPS高精度时间", GPS_high_accuracy_time()}); + ret.append({"启动以来TX队列溢出", TX_queue_overflow_since_startup()}); + ret.append({"过去一秒TX队列溢出", TX_queue_overflow_last_second()}); + ret.append({"发现过多NMEA数据", excessive_NMEA_found()}); + return ret; + } + + + static double decodeLatitudeFromBAM(const std::string& bigdian4) { + std::uint32_t value; + yC::big_endian_2_platform(bigdian4, (char*)&value); + // BAM 格式解码公式 + // return (static_cast(value) / pow(2, 32)) * 360.0; + return (static_cast(value) / pow(2, 32)) * 360.0; + } + std::string toBinary() { + std::string binary = {0x1a, 0x48, 0x01, 0x18}; + binary.append(yC::platform_2_big_endian(&SerNum, 4)); + binary.append(yC::platform_2_big_endian(&Flags, 2)); + binary.append(std::string((char*)&I_U_, 2)); // 内部使用没说什么序 默认大端序 + binary.append(yC::platform_2_big_endian(&xTime, 4)); + binary.append(yC::platform_2_big_endian(&Lat, 4)); + binary.append(yC::platform_2_big_endian(&Lon, 4)); + binary.append(yC::platform_2_big_endian(&Alt, 2)); + // 没说什么序 一字节 大端小端 等同 + binary.append({(char)Sat}); + binary.append({(char)HDOP}); + return binary; + } +}; + + + +static void hulc_big_endian_set(const std::string& msg, void* field, int start, int len) { + auto str = msg.substr(start, len); + yC::big_endian_2_platform(str, (char*)field); +} + +static void hulc_set2(const std::string& msg, void* field, int start, int len) { + auto it = msg.substr(start, len); + std::memcpy(field, it.data(), len); +} +// msg 是文本二进制字符串 转义后的 1a34 +static HULC_Status_Message create_HULC_Status_Message(const std::string& msg) { + HULC_Status_Message ret{}; + hulc_big_endian_set(msg, &ret.SerNum, 4, 4); + hulc_big_endian_set(msg, &ret.Flags, 8, 2); + hulc_set2(msg, &ret.I_U_, 10, 2); + hulc_big_endian_set(msg, &ret.xTime, 12, 4); + hulc_big_endian_set(msg, &ret.Lat, 16, 4); + hulc_big_endian_set(msg, &ret.Lon, 20, 4); + hulc_big_endian_set(msg, &ret.Alt, 24, 2); + hulc_set2(msg, &ret.Sat, 26, 1); + hulc_set2(msg, &ret.HDOP, 27, 1); + return ret; +} + + +struct HULC_Reply_to_command : HULC_Message { + std::uint8_t CMD; + std::uint8_t POO_14; +}; + +// 0x1A 0x48 0x01 0x18 SerNum Flags I.U. xTime Lat Lon Alt Sat HDOP +inline std::uint8_t get_id(const std::string& msg) { + return msg[2]; +} + +inline std::uint8_t get_len(const std::string& msg) { + return msg[3]; +} + +// Type Payload Size Description +// 0x31 4 bytes Mode-A/C raw data +// 0x32 7 bytes Mode-S Short Squitter raw data +// 0x33 14 bytes Mode-S Extended Squitter raw data +// 0x48 variable HULC Message, see below for details +// HULC Protocol uses the byte 0x1A as start-of-packet (SOP) marker. In order to avoid misinterpretation +// of normal data bytes with value 0x1A as SOP, each occurrence of data byte 0x1A is doubled in the +// data stream during transmission ('escaping'). It is thus necessary during reception to detect every +// 0x1A not followed by another 0x1A as an SOP marker and to reduce every occurrence of a double +// 0x1A to a single 0x1A data byte ('un-escaping'). +// Example of a 4-byte long packet including SOP marker and 0x1A data bytes: +// Before Escaping: 1A 32 27 1A E8 57 F0 1A 6C +// During Transmission: 1A 32 27 1A 1A E8 57 F0 1A 1A 6C +// After Un-Escaping: 1A 32 27 1A E8 57 F0 1A 6C +void Binary_Format_handle_buffer(std::string& buffer, const std::string& data, const std::function& callback); +/* Command Message (Host ==> Device) +The command-message has a simple ASCII based structure. Each message starts with a ‘#’ (0x23) and +ends with ‘’ (0x0D, 0x0A). In-between are one to 16 bytes in 2-digit hexadecimal representation +using upper-case letters, separated by ‘-’ (0x2D). +For example: + ‘#00’ or hexadecimal 0x23 0x30 0x30 0x0D 0x0A + ‘#43-02’ or hexadecimal 0x23 0x34 0x33 0x2D 0x30 0x32 0x0D 0x0A + The first byte is mandatory and holds the command while the remaining bytes are parameters. + Available commands depend on the selected protocol and are discussed in the respective protocol + section. +*/ + +std::string mode_s_msg_packet_to_server_packet(std::uint32_t id, std::string packet); + + + +// 1a34 radarcape 状态帧消息 +// https://wiki.jetvision.de/wiki/Radarcape:Version_History#Status_Frame_0x34_contents_were_extended_with_GPS_and_UTC_information +// 状态帧0x34内容已扩展为 GPS 和 UTC 信息 +// 当选择 GPS 时间戳时,状态帧由 1PPS 脉冲触发,在传统 12MHz 时间戳的情况下随机触发。 +// 状态帧包含有效的时间戳 +// 信号电平值为零/保留 + + +#define SETGET(name, pos) void set_##name(bool value) { set(pos, value); } \ +bool get_##name() { return get(pos); } + +#define SETGET2(Type, name, pos) void set_##name(Type value) { set(pos, (uint8_t)value); } \ +Type get_##name() { return (Type)get(pos); } + +struct BYTE_Settings { +protected: + uint8_t settings{}; // 8 位的字段,存储所有设置 + void set(uint8_t pos, bool value) { + if (value) { + // 设置指定位置的位为 1 + settings |= (1 << pos); + } else { + // 设置指定位置的位为 0 + settings &= ~(1 << pos); + } + } + [[nodiscard]] bool get(uint8_t pos) const { + // 获取指定位置的位值 + return (settings >> pos) & 0x01; + } +}; +struct FPGA_Setting : BYTE_Settings { + enum class Data_Output_Mode { AVR_hexdump, Binary }; + enum class Raw_Data_Frame_Type { No_Pre_Filtering, DF11_17_18_only }; + enum class Timestamp_Mode { Legacy_12MHz, GPS }; + SETGET2(Data_Output_Mode, data_output_mode, 0) + SETGET2(Raw_Data_Frame_Type, raw_data_frame_type, 1) + SETGET(timestamps_within_AVR_format, 2) + SETGET2(Timestamp_Mode, timestamp_mode, 4) + SETGET(serial_flow_control, 5) + SETGET(FEC, 6) // 1位转发错误校正 + SETGET(mode_ac_decoding , 7) + Json toJson() { + Json ret = Json::object(); + ret.append({"Data_Output_Mode", to_string(get_data_output_mode())}); + ret.append({"Raw_Data_Frame_Type", to_string(get_raw_data_frame_type())}); + ret.append({"timestamps_within_AVR_format", get_timestamps_within_AVR_format()}); + ret.append({"timestamps_mode", to_string(get_timestamp_mode())}); + ret.append({"serial_flow_control", get_serial_flow_control()}); + ret.append({"1位转发错误校正", get_FEC()}); + ret.append({"mode_ac_decoding", get_mode_ac_decoding()}); + return ret; + } + +}; + +struct GPS_Setting : BYTE_Settings { + enum class Time_Value_Base { UTC, GPS }; + enum class Daysec_Range { R_0_86399, R_1_86400 }; + SETGET(antenna_status, 0) // 位 0: 天线状态 + SETGET(tracking_at_least_1_satellite, 1) // 位 1: 至少跟踪 1 颗卫星 + SETGET(tracking_at_least_3_satellites, 2) // 位 2: 至少跟踪 3 颗卫星 + SETGET2(Time_Value_Base, time_value_base, 3) // 位 3: 时间值基数 (UTC/GPS) + SETGET(unused4, 4) // 位 4: 保留 + SETGET(unused5, 5) // 位 5: 保留 + SETGET(unused6, 6) // 位 6: 保留 + SETGET2(Daysec_Range, daysec_range, 7) // 位 7: Daysec 值范围 + Json toJson() { + Json ret = Json::object(); // 创建 JSON 对象 + ret.append({"antenna_status", get_antenna_status()}); + ret.append({"至少跟踪 1 颗卫星", get_tracking_at_least_1_satellite()}); + ret.append({"至少跟踪 3 颗卫星", get_tracking_at_least_3_satellites()}); + ret.append({"timestamps_mode", to_string(get_time_value_base())}); + ret.append({"daysec_range", to_string(get_daysec_range())}); + return ret; + } +}; + +struct Radarcape_STATUS_Message { + FPGA_Setting fpga_settings; + std::uint8_t pps_nanosecond_offset{}; // 包含 1PPS 事件时纳秒计时器的偏移量,单位为 15.625ns。正常值范围为 -3 ... +3。 + GPS_Setting gps_settings; + void set_pps_nanosecond(double value) { + if (value < 0.0 || value > 4000) { + std::cerr << "Invalid value. Must be in range [0.0, 4000.0]." << std::endl; + return; + } + pps_nanosecond_offset = static_cast((value)/15.625); + } + [[nodiscard]] double get_pps_nanosecond() const { + return (double)pps_nanosecond_offset * 15.625; + } + std::string toBinary() { + std::string binary = {0x1a, 0x34}; + binary.append(reinterpret_cast(&fpga_settings), 1); + binary.append(reinterpret_cast(&pps_nanosecond_offset), 1); + binary.append(reinterpret_cast(&gps_settings), 1); + return binary; + } + + Json toJson() { + Json ret = Json::object(); + ret.append({"pps_nanosecond_offset", get_pps_nanosecond()}); + ret.append({"fpga_settings", fpga_settings.toJson()}); + ret.append({"gps_settings", gps_settings.toJson()}); + return ret; + } + +}; + +// 消息5字节 +// +static Radarcape_STATUS_Message create_Radarcape_STATUS_Message(const std::string& msg_5) { + std::string msg = yC::big_endian_2_platform(msg_5.substr(2)); + Radarcape_STATUS_Message ret{}; + std::memcpy(&ret, (void*)(msg.data()), 3); + return ret; +} + + + +#undef SETGET +#undef SETGET2 + +#endif diff --git a/module/dll_source_lzy/114/out/lib/hulc/Json.cpp b/module/dll_source_lzy/114/out/lib/hulc/Json.cpp new file mode 100644 index 0000000..8949c43 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/Json.cpp @@ -0,0 +1,420 @@ +#include +#include +#include "Json.h" +#include +#include +std::function Json::handle_error = [](const char* function_name, const std::string& reason, const char* file, int line) { + std::ostringstream oss; + oss << "Json::" << function_name << " 原因:" << reason << std::endl; + oss << "位置:" << file << ":" << line << std::endl; + //throw std::runtime_error(oss.str().c_str()); + std::cout << oss.str(); + exit(-1); +}; + +#define HANDLE_ERROR(reason) Json::handle_error(__func__, reason, __FILE__, __LINE__); +void skip_whitespace(std::string& s, size_t& i); +std::string parse_string(std::string& s, size_t& i); +std::string parse_number(std::string& s, size_t& i); +std::optional parse_object(std::string& s, size_t& i); +std::optional parse_array(std::string& s, size_t& i); + +std::ostream& operator<<(std::ostream& os, const Json& obj) { + os << const_cast(obj).to_json_string(); + return os; +} + +Json::operator std::string() const { + return const_cast(this)->to_json_string(); +} + +std::string Json::get_string(const std::string& key) const { + const Json* that = get(key); + if (!that) { + HANDLE_ERROR(key + " not found! " + to_json_string()) + return ""; + } + return that->val; +} + +bool Json::bool_val() const { + if (val != "true" && val != "false") { + HANDLE_ERROR("Invalid bool argument: " + val) + } + return (val == "true"); +} + +const std::string& Json::get_inside_string(const std::string& key) const { + const Json* that = get(key); + if (!that) { + HANDLE_ERROR(key + " not found!") + return that->val; + } + return that->val; +} + +Json::Json(std::string key, const char* val) : key(std::move(key)), val(std::string(val)), valueType(String) { +} + +Json::Json(std::string key, std::string val) : key(std::move(key)), val(std::move(val)), valueType(String) { +} + +Json::Json(std::string key, bool val) : key(std::move(key)), val(val ? "true" : "false"), valueType(Bool) { +} + +Json::Json(std::string key, const Json& children) : + key(std::move(key)), val(children.val), valueType(children.valueType), children(children.children) {} + + +void Json::append_list(const std::vector& arr) { +#ifdef _DEBUG + if(valueType != JsonType::Object && valueType != JsonType::Array){ + HANDLE_ERROR("append_list error! key:" + key + " not array or object " + to_json_string()) + } +#else + +#endif + for (const Json& j : arr) { + children.push_back(j); + } +} + +void Json::append(const Json& json) { +#ifdef _DEBUG + if (valueType != JsonType::Object && valueType != JsonType::Array) + { + HANDLE_ERROR("append_list error! key:" + key + " not array or object " + to_json_string()) + } +#else +#endif + children.push_back(json); +} +void Json::prepend(const Json& json) { +#ifdef _DEBUG + if (valueType != JsonType::Object && valueType != JsonType::Array) + { + HANDLE_ERROR("append_list error! key:" + key + " not array or object " + to_json_string()) + } +#else +#endif + children.insert(children.begin(), json); +} +Json::Json(const char* val) : val(std::string(val)), valueType(String) { +} + +Json::Json(std::string val) : val(std::move(val)), valueType(String) { +} + +Json::Json(const int& val) : val(std::to_string(val)), valueType(Number) { +} + +Json::Json(const double& val) : val(std::to_string(val)), valueType(Number) { +} + +Json::Json(const bool& val) : val(val ? "true" : "false"), valueType(Bool) { +} + +const Json* Json::get(const std::string& key) const { + for (const Json& child : children) { + if (child.key == key) { + return &child; + } + } + HANDLE_ERROR(key + " not found!" + to_json_string()) + return nullptr; +} + +Json Json::array(const std::vector& children) { + return array("", children); +} + +Json Json::object(const std::vector& children) { + return object("", children); +} + +Json Json::array(std::string key, const std::vector& children) { + Json ret; + ret.key = std::move(key); + ret.valueType = Array; + ret.children = children; + return ret; +} + +Json Json::object(std::string key, const std::vector& children) { + Json ret; + ret.key = std::move(key); + ret.valueType = Object; + ret.children = children; + return ret; +} + +std::string Json::get_indent(const int& indent, const std::string& indentStr) { + std::string ret; + for (int i = 0; i < indent; ++i) { + ret.append(indentStr); + } + return ret; +} + +void Json::append_value_string(std::string& ret, const int& indent, bool isEnd, const std::string& object_split, + const std::string& array_split, const std::string& indentStr, JsonType fatherType) const { + ret.append(get_indent(indent, indentStr)); + if (fatherType == Object) ret.append("\"" + key + "\"" + ": "); + const std::string split = fatherType == Array ? array_split : object_split; + if (valueType == String) { + ret.append("\"" + val + "\""); + goto end; + } + if (valueType == Null) { + ret.append("null"); + goto end; + } + if (valueType == Number || valueType == Bool) { + ret.append(val); + goto end; + } + if (valueType == Object) { + ret.append("{" + object_split); + size_t n = children.size(); + if (n) { + for (size_t i = 0; i < n - 1; ++i) { + children[i].append_value_string(ret, indent + 2, false, object_split, array_split, indentStr, Object); + } + children[n - 1].append_value_string(ret, indent + 2, true, object_split, array_split, indentStr, Object); + } + ret.append(get_indent(indent, indentStr) + "}"); + goto end; + } + if (valueType == Array) { + ret.append("[" + array_split); + size_t n = children.size(); + if (n) { + for (size_t i = 0; i < n - 1; ++i) { + children[i].append_value_string(ret, indent + 1, false, object_split, array_split, indentStr, Array); + } + children[n - 1].append_value_string(ret, indent + 1, true, object_split, array_split, indentStr, Array); + } + ret.append(get_indent(indent, indentStr) + "]"); + goto end; + } +end: if (!isEnd) ret.append(","); + ret.append(split); +} + + +void handleBlank(std::string& s) { + int n = static_cast(s.length()); + bool isInStr = false; + for (int i = n - 1; i >= 0; --i) { + const char& c = s[i]; + if (c == '\"') isInStr = !isInStr; + if (isInStr) continue; + if (c == '\n' || c == '\t' || c == ' ') { + s.erase(i, 1); + } + } +} + +std::string Json::to_json_string(const std::string& object_split, const std::string& array_split, + const std::string& indentStr, const int& indent) const { + if (valueType == Bool) return val; + if (valueType == String) return '\"' + val + '\"'; + if (valueType == Number) return val; + if (valueType == Null) { + if (children.size() != 0) { + HANDLE_ERROR( "null 带有children 转成了字符串 (" + key + "," + val + ")" + to_json_string()) + } + return "null"; + } + std::string ret; + //ret.append(split); + append_value_string(ret, indent, true, object_split, array_split, indentStr, Null); + return ret; +} + +Json::Json(std::nullptr_t) : valueType(Null), val("null") { +} + +Json::Json(std::string key, std::nullptr_t) : key(std::move(key)), valueType(Null), val("null") { +} + +Json::Json() = default; +// 解析的分割线---------------------------- + + +// 如果未找到非空白字符,将 i 更新为 std::string::npos +void skip_whitespace(std::string& s, size_t& i) { + i = s.find_first_not_of(" \t\n\r\f\v", i); +} + +// 传入的 i 是 "所在的index +std::string parse_string(std::string& s, size_t& i) { + i++; //skip '"' + if (s[i] == '"') { + i++; + return ""; + } + std::string ret; + while (true) { + if (s[i] == '"' && s[i - 1] != '\\') { + break; + } + ret += s[i]; + i++; + } + i++; + return ret; +} + +std::string parse_number(std::string& s, size_t& i) { + std::string num; + while (isdigit(s[i])) { + num += s[i++]; + } + if (s[i] == '.') { + i++; + num += '.'; + while (isdigit(s[i])) { + num += s[i++]; + } + } + return num; +} + + +std::optional parse_json_EX(std::string& s, size_t& i); +std::optional parse_json(const std::string& s) { + auto& t = const_cast(s); + size_t i = 0; + return parse_json_EX(t, i); +} + +std::optional parse_json_EX(std::string& s, size_t& i) { + skip_whitespace(s, i); + switch (s[i]) { + case 'n': i += 4; + return nullptr; + case 't': i += 4; + return true; + case 'f': i += 5; + return false; + case '{': return parse_object(s, i); + case '[': return parse_array(s, i); + case '"': return parse_string(s, i); + case '-': { + i++; //skip - + Json ret(nullptr); + ret.valueType = Json::Number; + ret.val = '-' + parse_number(s, i); + return ret; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + Json ret(nullptr); + ret.valueType = Json::Number; + ret.val = parse_number(s, i); + return ret; + } + default: { + HANDLE_ERROR("json 解析错误! s:【" + s + "】\n") + return std::nullopt; + } + } +} + +std::optional parse_object(std::string& s, size_t& i) { + i++; // skip '{' + skip_whitespace(s, i); + Json ret = Json::object(); + if (s[i] == '}') { + i++; + return ret; + } + while (true) { + skip_whitespace(s, i); + std::string key = parse_string(s, i); + skip_whitespace(s, i); + if (s[i] != ':') { + HANDLE_ERROR("格式错误!") + return std::nullopt; + } + i++; + skip_whitespace(s, i); + std::optional o_kv = parse_json_EX(s, i); + if(!o_kv.has_value()){ + return std::nullopt; + } + auto& kv = o_kv.value(); + skip_whitespace(s, i); + kv.key = key; + ret.children.push_back(kv); + if (s[i] == '}') { + i++; + break; + } + if (s[i] == ',') { + i++; + } + } + return ret; +} + +std::optional parse_array(std::string& s, size_t& i) { + i++; // skip '[' + skip_whitespace(s, i); + Json ret = Json::array(); + if (s[i] == ']') { + i++; + return ret; + } + while (true) { + skip_whitespace(s, i); + // parseJson(s, i) + std::optional o_kv = parse_json_EX(s, i); + if(!o_kv.has_value()){ + return std::nullopt; + } + auto& kv = o_kv.value(); + ret.children.push_back(kv); + skip_whitespace(s, i); + if (s[i] == ']') { + i++; + break; + } + if (s[i] == ',') { + i++; + } + } + return ret; +} +std::optional parse_json_file(std::string& fileName) { + static auto readFile = [](const std::string& path) -> std::string { + std::ifstream infile(path, std::ios::binary | std::ios::ate); + if (!infile.is_open()) { + HANDLE_ERROR("找不到文件路径!" + path) + return ""; + } + std::streampos fileSize = infile.tellg(); + infile.seekg(0, std::ios::beg); // 将文件指针移回文件开始位置 + std::string content(fileSize, '\0'); // 预分配足够的空间 + infile.read(&content[0], fileSize); // 读取文件到字符串中 + infile.close(); + return content; + }; + return parse_json(readFile(fileName)); +} + +std::optional parse_json_file(std::string&& fileName){ + return parse_json_file(fileName); +} + +#undef HANDLE_ERROR + diff --git a/module/dll_source_lzy/114/out/lib/hulc/Json.h b/module/dll_source_lzy/114/out/lib/hulc/Json.h new file mode 100644 index 0000000..01b44a7 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/Json.h @@ -0,0 +1,142 @@ +#ifndef Json_H +#define Json_H +#include +#include +#include +#include +#include +#include +#include +#define Json_GET(type, name) [[nodiscard]] type get_##name(const std::string &key) const { \ + const Json* that = get(key); \ + if (!that) { \ + std::cout << "get" #type " error json not found! key:" << key << " json: " << to_json_string() << std::endl; \ + return false; \ + } \ + return that-> name##_val(); \ +} + + +template +T_Number to_number(const std::string& val) { + if constexpr (std::is_integral::value) { + if constexpr (std::is_unsigned::value) { + return static_cast(std::stoull(val)); + } else if constexpr (std::is_signed::value) { + return static_cast(std::stoll(val)); + } + } else if constexpr (std::is_floating_point::value) { + return static_cast(std::stold(val)); + } + return T_Number(); +} + + +class Json { +public: + static std::function handle_error; + explicit operator std::string() const; + friend std::ostream& operator<<(std::ostream& os, const Json& obj); + [[nodiscard]] const Json* get(const std::string& key) const; + [[nodiscard]] std::string get_string(const std::string& key) const; + [[nodiscard]] const std::string& get_inside_string(const std::string& key) const; + //键值对 + Json(std::string key, const char* charArrayVal); + Json(std::string key, std::nullptr_t); + Json(std::string key, std::string stringVal); + Json(std::string key, bool boolVal); + Json(std::string key, const Json& body); + + [[nodiscard]] bool bool_val() const; + Json_GET(bool, bool) + template + T_Number get_number(const std::string& key) const { + const Json* that = get(key); + if (!that) { + std::cout << "json not found! key:" << key << " json: " << to_json_string() << std::endl; + return false; + } + return that->number_val(); + } + template + T_Number number_val() const { + return to_number(val); + } + // template + // Json(std::string key, const T_Number& intVal) : key(std::move(key)), + // val(std::to_string(intVal)), valueType(Number) { + // } + + template + Json(std::string key, const T_Number& intVal, typename std::enable_if< + !std::is_same::value && + !std::is_same>::value + >::type* = nullptr) + : key(std::move(key)), val(std::to_string(intVal)), valueType(Number) { + } + + + void append_list(const std::vector& arr); + void append(const Json& json); + void prepend(const Json& json); + static Json array(std::string key, const std::vector& arr); + static Json object(std::string key, const std::vector& children); + static Json array(const std::vector& arr = {}); + static Json object(const std::vector& children = {}); + //不加explicit 让他允许隐式类型转换 + Json(std::nullptr_t); //只能填写 nullptr + Json(const char* charArrayVal); + Json(std::string stringVal); + Json(const int& intVal); + Json(const double& doubleVal); + Json(const bool& boolVal); + + enum JsonType { + String, + Object, + Array, + Null, + Number, + Bool + } valueType = Null; + + std::string key, val; + std::vector children; + [[nodiscard]] std::string to_json_string(const std::string& object_split = "\n", const std::string& array_split = "\n", const std::string& indentStr = " ", const int& indent = 0) const; + Json(); +private: + static std::string get_indent(const int& indent, const std::string& indentStr); + void append_value_string(std::string& ret, const int& indent, bool isEnd, const std::string& object_split, + const std::string& array_split, const std::string& indentStr, JsonType fatherType) const; +}; + +std::optional parse_json(const std::string& s); +std::optional parse_json_file(std::string& fileName); +std::optional parse_json_file(std::string&& fileName); + + + +#undef Json_GET + +// #define J(FIELD) Json(#FIELD, this->FIELD) +// #define JEnum(FIELD) Json(#FIELD, to_string(this->FIELD)) + +#define Ret_J(FIELD) ret.append(Json(#FIELD, this->FIELD)); +#define Ret_JEnum(FIELD) ret.append(Json(#FIELD, to_string(this->FIELD))); + +#define ORet_J(FIELD) if(FIELD.has_value()) ret.append(Json(#FIELD, FIELD.value())); \ +else ret.append(Json(#FIELD, nullptr)); + +#define ORet_JEnum(FIELD) if(FIELD.has_value()) ret.append(Json(#FIELD, to_string(FIELD.value()))); \ +else ret.append(Json(#FIELD, nullptr)); + +#define ORet_J2(FIELD) if(FIELD.has_value()) ret.append(Json(#FIELD, FIELD.value())); + + +#define ORet_JEnum2(FIELD) if(FIELD.has_value()) ret.append(Json(#FIELD, to_string(FIELD.value()))); + + +#define J_Get_Number(FIELD) FIELD = that_json->get_number(#FIELD); + +#define JA_Get_Number(FIELD) FIELD.store(that_json->get_number(#FIELD)); +#endif diff --git a/module/dll_source_lzy/114/out/lib/hulc/base_format.h b/module/dll_source_lzy/114/out/lib/hulc/base_format.h new file mode 100644 index 0000000..6708353 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/base_format.h @@ -0,0 +1,74 @@ + +#ifndef BASE_FORMAT_H +#define BASE_FORMAT_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Bit.h" +#include "Json.h" +#include "magic_enum/export.h" + + +// 1纳秒是 10 -9次方秒 +struct MLAT_timestamp { + explicit MLAT_timestamp(); + explicit MLAT_timestamp(const std::string& memory_6); + MLAT_timestamp(std::uint32_t daysec, std::uint32_t nanosec); + std::uint32_t daysec{}; // 当天的秒数 高18位 + std::uint32_t nanosec{}; // 当天的纳秒数 低30位 + std::string to_bin_string() const; + std::string to_memory() const; + unsigned long hh{}; + unsigned long mm{}; + unsigned long ss{}; + std::string memory{}; + bool operator<(const MLAT_timestamp& other) const { + // 比较 daysec(高18位) + if (daysec != other.daysec) { + return daysec < other.daysec; + } + // 如果 daysec 相等,比较 nanosec(低30位) + return nanosec < other.nanosec; + } + + + [[nodiscard]] Json toJson() const { + Json ret = Json::object(); + ret.append({"秒", daysec}); + ret.append({"纳秒", nanosec}); + ret.append({"时间", std::to_string(hh) + ":" + std::to_string(mm) + ":" + std::to_string(ss)}); + return ret; + } + + [[nodiscard]] std::string to_string() const { + return + "{" + std::to_string(daysec) + "," + std::to_string(nanosec) + "}_" + + "[" + + std::to_string(hh) + "时:" + + std::to_string(mm) + "分:" + + std::to_string(ss) + "秒" + "]"; + } + + void parse_hhmmss(); +}; + +#endif diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/export.h b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/export.h new file mode 100644 index 0000000..a6c579c --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/export.h @@ -0,0 +1,114 @@ +#ifndef yc_magic_enum_H +#define yc_magic_enum_H +#include "magic_enum/magic_enum.hpp" +#include +#include + +template +EnumType to_enum(const std::string& str) { + auto opt = magic_enum::enum_cast(str); + if (opt.has_value()) { + return opt.value(); // 返回枚举值 + } + std::cerr << std::string(magic_enum::enum_type_name()) +"字符串转换枚举值错误!str:" + str + "\n"; + exit(-1); +} + +template +std::optional to_enum2(const std::string& str) { + auto opt = magic_enum::enum_cast(str); + if (opt.has_value()) { + return opt.value(); // 返回枚举值 + } + return std::nullopt; +} + +template +std::string to_string(EnumType type) { + return std::string(magic_enum::enum_name(type)); +} + +template +std::optional int_to_enum(int enum_value) { + // 静态断言,确保 EnumType 是枚举类型 + static_assert(std::is_enum_v, "EnumType must be an enum type"); + // 使用 magic_enum 获取所有的枚举值 + auto enum_values = magic_enum::enum_values(); + // 检查是否存在匹配的枚举值 + for (auto value : enum_values) { + if (static_cast(value) == enum_value) { + return value; // 返回匹配的枚举值 + } + } + // 如果没有匹配的枚举值,返回 std::nullopt + return std::nullopt; +} + +template +std::optional enum_int_to_string(int enum_value) { + auto tmp = int_to_enum(enum_value); + if (!tmp.has_value()) return std::nullopt; + return to_string(tmp.value()); +} + +template +std::vector split_flags(Enum flags) +{ + static_assert(std::is_enum_v, "Template parameter must be an enum type"); + std::vector setFlags; + for (auto flag : magic_enum::enum_values()) + { + if ((flags & flag) == flag) + { + setFlags.push_back(flag); + } + } + return setFlags; +} + + +inline std::string to_string(const char* val) { + return std::string(val); +} + +template <> +inline std::string to_string(std::string val) { + return val; +} +template <> +inline std::string to_string(bool val) { + return val ? "true" : "false"; +} +template <> +inline std::string to_string(void* val) { + std::ostringstream oss; + oss << "0x" << std::hex << std::showbase << reinterpret_cast(val); + return oss.str(); +} + +#define IMPL_to_string_from_std(Type) template <> \ +inline std::string to_string(Type val) { \ + return std::to_string(val); \ +} \ + +IMPL_to_string_from_std(signed char) +IMPL_to_string_from_std(short) +IMPL_to_string_from_std(int) +IMPL_to_string_from_std(long) +IMPL_to_string_from_std(long long) +IMPL_to_string_from_std(unsigned char) +IMPL_to_string_from_std(unsigned short) +IMPL_to_string_from_std(unsigned int) +IMPL_to_string_from_std(unsigned long) +IMPL_to_string_from_std(unsigned long long) +IMPL_to_string_from_std(float) +IMPL_to_string_from_std(double) +IMPL_to_string_from_std(long double) +#undef IMPL_to_string_from_std + + + + + + +#endif \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/LICENSE b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/LICENSE new file mode 100644 index 0000000..20acf2f --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 - 2024 Daniil Goncharov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum.hpp b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum.hpp new file mode 100644 index 0000000..158207e --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum.hpp @@ -0,0 +1,1508 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_HPP +#define NEARGYE_MAGIC_ENUM_HPP + +#define MAGIC_ENUM_VERSION_MAJOR 0 +#define MAGIC_ENUM_VERSION_MINOR 9 +#define MAGIC_ENUM_VERSION_PATCH 7 + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(MAGIC_ENUM_CONFIG_FILE) +# include MAGIC_ENUM_CONFIG_FILE +#endif + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +# include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING) +# include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +# include +#endif +#endif + +#if defined(MAGIC_ENUM_NO_ASSERT) +# define MAGIC_ENUM_ASSERT(...) static_cast(0) +#elif !defined(MAGIC_ENUM_ASSERT) +# include +# define MAGIC_ENUM_ASSERT(...) assert((__VA_ARGS__)) +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunknown-warning-option" +# pragma clang diagnostic ignored "-Wenum-constexpr-conversion" +# pragma clang diagnostic ignored "-Wuseless-cast" // suppresses 'static_cast('\0')' for char_type = char (common on Linux). +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. +# pragma GCC diagnostic ignored "-Wuseless-cast" // suppresses 'static_cast('\0')' for char_type = char (common on Linux). +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 26495) // Variable 'static_str::chars_' is uninitialized. +# pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. +# pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call. +# pragma warning(disable : 4514) // Unreferenced inline function has been removed. +#endif + +// Checks magic_enum compiler compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 || defined(__RESHARPER__) +# undef MAGIC_ENUM_SUPPORTED +# define MAGIC_ENUM_SUPPORTED 1 +#endif + +// Checks magic_enum compiler aliases compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920 +# undef MAGIC_ENUM_SUPPORTED_ALIASES +# define MAGIC_ENUM_SUPPORTED_ALIASES 1 +#endif + +// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128. +// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN. +#if !defined(MAGIC_ENUM_RANGE_MIN) +# define MAGIC_ENUM_RANGE_MIN -128 +#endif + +// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 127. +// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX. +#if !defined(MAGIC_ENUM_RANGE_MAX) +# define MAGIC_ENUM_RANGE_MAX 127 +#endif + +// Improve ReSharper C++ intellisense performance with builtins, avoiding unnecessary template instantiations. +#if defined(__RESHARPER__) +# undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +# undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +# if __RESHARPER__ >= 20230100 +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) __rscpp_enumerator_name(V) +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) __rscpp_type_name() +# else +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) nullptr +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) nullptr +# endif +#endif + +namespace magic_enum { + +// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL. +#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +MAGIC_ENUM_USING_ALIAS_OPTIONAL +#else +using std::optional; +#endif + +// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +MAGIC_ENUM_USING_ALIAS_STRING_VIEW +#else +using std::string_view; +#endif + +// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING) +MAGIC_ENUM_USING_ALIAS_STRING +#else +using std::string; +#endif + +using char_type = string_view::value_type; +static_assert(std::is_same_v, "magic_enum::customize requires same string_view::value_type and string::value_type"); +static_assert([] { + if constexpr (std::is_same_v) { + constexpr const char c[] = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + constexpr const wchar_t wc[] = L"abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + static_assert(std::size(c) == std::size(wc), "magic_enum::customize identifier characters are multichars in wchar_t."); + + for (std::size_t i = 0; i < std::size(c); ++i) { + if (c[i] != wc[i]) { + return false; + } + } + } + return true; +} (), "magic_enum::customize wchar_t is not compatible with ASCII."); + +namespace customize { + +// Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 127. +// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX. +// If need another range for specific enum type, add specialization enum_range for necessary enum type. +template +struct enum_range { + static constexpr int min = MAGIC_ENUM_RANGE_MIN; + static constexpr int max = MAGIC_ENUM_RANGE_MAX; +}; + +static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); + +namespace detail { + +enum class customize_tag { + default_tag, + invalid_tag, + custom_tag +}; + +} // namespace magic_enum::customize::detail + +class customize_t : public std::pair { + public: + constexpr customize_t(string_view srt) : std::pair{detail::customize_tag::custom_tag, srt} {} + constexpr customize_t(const char_type* srt) : customize_t{string_view{srt}} {} + constexpr customize_t(detail::customize_tag tag) : std::pair{tag, string_view{}} { + MAGIC_ENUM_ASSERT(tag != detail::customize_tag::custom_tag); + } +}; + +// Default customize. +inline constexpr auto default_tag = customize_t{detail::customize_tag::default_tag}; +// Invalid customize. +inline constexpr auto invalid_tag = customize_t{detail::customize_tag::invalid_tag}; + +// If need custom names for enum, add specialization enum_name for necessary enum type. +template +constexpr customize_t enum_name(E) noexcept { + return default_tag; +} + +// If need custom type name for enum, add specialization enum_type_name for necessary enum type. +template +constexpr customize_t enum_type_name() noexcept { + return default_tag; +} + +} // namespace magic_enum::customize + +namespace detail { + +template +struct supported +#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template , std::enable_if_t, int> = 0> +using enum_constant = std::integral_constant; + +template +inline constexpr bool always_false_v = false; + +template +struct has_is_flags : std::false_type {}; + +template +struct has_is_flags::is_flags)>> : std::bool_constant::is_flags)>>> {}; + +template +struct range_min : std::integral_constant {}; + +template +struct range_min::min)>> : std::integral_constant::min), customize::enum_range::min> {}; + +template +struct range_max : std::integral_constant {}; + +template +struct range_max::max)>> : std::integral_constant::max), customize::enum_range::max> {}; + +struct str_view { + const char* str_ = nullptr; + std::size_t size_ = 0; +}; + +template +class static_str { + public: + constexpr explicit static_str(str_view str) noexcept : static_str{str.str_, std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size_ == N); + } + + constexpr explicit static_str(string_view str) noexcept : static_str{str.data(), std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size() == N); + } + + constexpr const char_type* data() const noexcept { return chars_; } + + constexpr std::uint16_t size() const noexcept { return N; } + + constexpr operator string_view() const noexcept { return {data(), size()}; } + + private: + template + constexpr static_str(const char* str, std::integer_sequence) noexcept : chars_{static_cast(str[I])..., static_cast('\0')} {} + + template + constexpr static_str(string_view str, std::integer_sequence) noexcept : chars_{str[I]..., static_cast('\0')} {} + + char_type chars_[static_cast(N) + 1]; +}; + +template <> +class static_str<0> { + public: + constexpr explicit static_str() = default; + + constexpr explicit static_str(str_view) noexcept {} + + constexpr explicit static_str(string_view) noexcept {} + + constexpr const char_type* data() const noexcept { return nullptr; } + + constexpr std::uint16_t size() const noexcept { return 0; } + + constexpr operator string_view() const noexcept { return {}; } +}; + +template > +class case_insensitive { + static constexpr char_type to_lower(char_type c) noexcept { + return (c >= static_cast('A') && c <= static_cast('Z')) ? static_cast(c + (static_cast('a') - static_cast('A'))) : c; + } + + public: + template + constexpr auto operator()(L lhs, R rhs) const noexcept -> std::enable_if_t, char_type> && std::is_same_v, char_type>, bool> { + return Op{}(to_lower(lhs), to_lower(rhs)); + } +}; + +constexpr std::size_t find(string_view str, char_type c) noexcept { +#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) +// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc +// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (workaround) { + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == c) { + return i; + } + } + + return string_view::npos; + } else { + return str.find(c); + } +} + +template +constexpr bool is_default_predicate() noexcept { + return std::is_same_v, std::equal_to> || + std::is_same_v, std::equal_to<>>; +} + +template +constexpr bool is_nothrow_invocable() { + return is_default_predicate() || + std::is_nothrow_invocable_r_v; +} + +template +constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(is_nothrow_invocable()) { +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (!is_default_predicate() || workaround) { + if (lhs.size() != rhs.size()) { + return false; + } + + const auto size = lhs.size(); + for (std::size_t i = 0; i < size; ++i) { + if (!p(lhs[i], rhs[i])) { + return false; + } + } + + return true; + } else { + return lhs == rhs; + } +} + +template +constexpr bool cmp_less(L lhs, R rhs) noexcept { + static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); + + if constexpr (std::is_signed_v == std::is_signed_v) { + // If same signedness (both signed or both unsigned). + return lhs < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return static_cast(lhs) < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return lhs < static_cast(rhs); + } else if constexpr (std::is_signed_v) { + // If 'right' is negative, then result is 'false', otherwise cast & compare. + return rhs > 0 && lhs < static_cast>(rhs); + } else { + // If 'left' is negative, then result is 'true', otherwise cast & compare. + return lhs < 0 || static_cast>(lhs) < rhs; + } +} + +template +constexpr I log2(I value) noexcept { + static_assert(std::is_integral_v, "magic_enum::detail::log2 requires integral type."); + + if constexpr (std::is_same_v) { // bool special case + return MAGIC_ENUM_ASSERT(false), value; + } else { + auto ret = I{0}; + for (; value > I{1}; value >>= I{1}, ++ret) {} + + return ret; + } +} + +#if defined(__cpp_lib_array_constexpr) && __cpp_lib_array_constexpr >= 201603L +# define MAGIC_ENUM_ARRAY_CONSTEXPR 1 +#else +template +constexpr std::array, N> to_array(T (&a)[N], std::index_sequence) noexcept { + return {{a[I]...}}; +} +#endif + +template +inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_GET_TYPE_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(E); + constexpr auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +#elif defined(__clang__) + str_view name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } +#elif defined(__GNUC__) + auto name = str_view{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 50; + name.str_ += 49; + } else { + name.size_ -= 40; + name.str_ += 37; + } +#elif defined(_MSC_VER) + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + str_view name; + name.str_ = __FUNCSIG__; + name.str_ += 40; + name.size_ += sizeof(__FUNCSIG__) - 57; +#else + auto name = str_view{}; +#endif + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + return name; + } else { + return str_view{}; // Unsupported compiler or Invalid customize. + } +} + +template +constexpr auto type_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_type_name(); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { + constexpr auto name = n(); + return static_str{name}; + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } +} + +template +inline constexpr auto type_name_v = type_name(); + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +#elif defined(__clang__) + str_view name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } + if (name.size_ > 22 && name.str_[0] == '(' && name.str_[1] == 'a' && name.str_[10] == ' ' && name.str_[22] == ':') { + name.size_ -= 23; + name.str_ += 23; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = str_view{}; + } +#elif defined(__GNUC__) + auto name = str_view{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 55; + name.str_ += 54; + } else { + name.size_ -= 40; + name.str_ += 37; + } + if (name.str_[0] == '(') { + name = str_view{}; + } +#elif defined(_MSC_VER) + str_view name; + if ((__FUNCSIG__[5] == '_' && __FUNCSIG__[35] != '(') || (__FUNCSIG__[5] == 'c' && __FUNCSIG__[41] != '(')) { + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + name.str_ = __FUNCSIG__; + name.str_ += 35; + name.size_ = sizeof(__FUNCSIG__) - 52; + } +#else + auto name = str_view{}; +#endif + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + return name; + } else { + return str_view{}; // Unsupported compiler or Invalid customize. + } +} + +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920 +# define MAGIC_ENUM_VS_2017_WORKAROUND 1 +#endif + +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + +# if defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +# else + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + str_view name; + name.str_ = __FUNCSIG__; + name.size_ = sizeof(__FUNCSIG__) - 17; + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ',' || name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = str_view{}; + } + return name; +# endif +} +#endif + +template +constexpr auto enum_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_name(V); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + constexpr auto name = n(); +#else + constexpr auto name = n(); +#endif + return static_str{name}; + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } +} + +template +inline constexpr auto enum_name_v = enum_name(); + +template +constexpr bool is_valid() noexcept { +#if defined(__clang__) && __clang_major__ >= 16 + // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 + constexpr E v = __builtin_bit_cast(E, V); +#else + constexpr E v = static_cast(V); +#endif + [[maybe_unused]] constexpr auto custom = customize::enum_name(v); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return name.size() != 0; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + return n().size_ != 0; +#else + return n().size_ != 0; +#endif + } else { + return false; + } +} + +enum class enum_subtype { + common, + flags +}; + +template > +constexpr U ualue(std::size_t i) noexcept { + if constexpr (std::is_same_v) { // bool special case + static_assert(O == 0, "magic_enum::detail::ualue requires valid offset."); + + return static_cast(i); + } else if constexpr (S == enum_subtype::flags) { + return static_cast(U{1} << static_cast(static_cast(i) + O)); + } else { + return static_cast(static_cast(i) + O); + } +} + +template > +constexpr E value(std::size_t i) noexcept { + return static_cast(ualue(i)); +} + +template > +constexpr int reflected_min() noexcept { + if constexpr (S == enum_subtype::flags) { + return 0; + } else { + constexpr auto lhs = range_min::value; + constexpr auto rhs = (std::numeric_limits::min)(); + + if constexpr (cmp_less(rhs, lhs)) { + return lhs; + } else { + return rhs; + } + } +} + +template > +constexpr int reflected_max() noexcept { + if constexpr (S == enum_subtype::flags) { + return std::numeric_limits::digits - 1; + } else { + constexpr auto lhs = range_max::value; + constexpr auto rhs = (std::numeric_limits::max)(); + + if constexpr (cmp_less(lhs, rhs)) { + return lhs; + } else { + return rhs; + } + } +} + +#define MAGIC_ENUM_FOR_EACH_256(T) \ + T( 0)T( 1)T( 2)T( 3)T( 4)T( 5)T( 6)T( 7)T( 8)T( 9)T( 10)T( 11)T( 12)T( 13)T( 14)T( 15)T( 16)T( 17)T( 18)T( 19)T( 20)T( 21)T( 22)T( 23)T( 24)T( 25)T( 26)T( 27)T( 28)T( 29)T( 30)T( 31) \ + T( 32)T( 33)T( 34)T( 35)T( 36)T( 37)T( 38)T( 39)T( 40)T( 41)T( 42)T( 43)T( 44)T( 45)T( 46)T( 47)T( 48)T( 49)T( 50)T( 51)T( 52)T( 53)T( 54)T( 55)T( 56)T( 57)T( 58)T( 59)T( 60)T( 61)T( 62)T( 63) \ + T( 64)T( 65)T( 66)T( 67)T( 68)T( 69)T( 70)T( 71)T( 72)T( 73)T( 74)T( 75)T( 76)T( 77)T( 78)T( 79)T( 80)T( 81)T( 82)T( 83)T( 84)T( 85)T( 86)T( 87)T( 88)T( 89)T( 90)T( 91)T( 92)T( 93)T( 94)T( 95) \ + T( 96)T( 97)T( 98)T( 99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \ + T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \ + T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \ + T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \ + T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255) + +template +constexpr void valid_count(bool* valid, std::size_t& count) noexcept { +#define MAGIC_ENUM_V(O) \ + if constexpr ((I + O) < Size) { \ + if constexpr (is_valid(I + O)>()) { \ + valid[I + O] = true; \ + ++count; \ + } \ + } + + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_V) + + if constexpr ((I + 256) < Size) { + valid_count(valid, count); + } +#undef MAGIC_ENUM_V +} + +template +struct valid_count_t { + std::size_t count = 0; + bool valid[N] = {}; +}; + +template +constexpr auto valid_count() noexcept { + valid_count_t vc; + valid_count(vc.valid, vc.count); + return vc; +} + +template +constexpr auto values() noexcept { + constexpr auto vc = valid_count(); + + if constexpr (vc.count > 0) { +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + std::array values = {}; +#else + E values[vc.count] = {}; +#endif + for (std::size_t i = 0, v = 0; v < vc.count; ++i) { + if (vc.valid[i]) { + values[v++] = value(i); + } + } +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + return values; +#else + return to_array(values, std::make_index_sequence{}); +#endif + } else { + return std::array{}; + } +} + +template > +constexpr auto values() noexcept { + constexpr auto min = reflected_min(); + constexpr auto max = reflected_max(); + constexpr auto range_size = max - min + 1; + static_assert(range_size > 0, "magic_enum::enum_range requires valid size."); + + return values(); +} + +template > +constexpr enum_subtype subtype(std::true_type) noexcept { + if constexpr (std::is_same_v) { // bool special case + return enum_subtype::common; + } else if constexpr (has_is_flags::value) { + return customize::enum_range::is_flags ? enum_subtype::flags : enum_subtype::common; + } else { +#if defined(MAGIC_ENUM_AUTO_IS_FLAGS) + constexpr auto flags_values = values(); + constexpr auto default_values = values(); + if (flags_values.size() == 0 || default_values.size() > flags_values.size()) { + return enum_subtype::common; + } + for (std::size_t i = 0; i < default_values.size(); ++i) { + const auto v = static_cast(default_values[i]); + if (v != 0 && (v & (v - 1)) != 0) { + return enum_subtype::common; + } + } + return enum_subtype::flags; +#else + return enum_subtype::common; +#endif + } +} + +template +constexpr enum_subtype subtype(std::false_type) noexcept { + // For non-enum type return default common subtype. + return enum_subtype::common; +} + +template > +inline constexpr auto subtype_v = subtype(std::is_enum{}); + +template +inline constexpr auto values_v = values(); + +template > +using values_t = decltype((values_v)); + +template +inline constexpr auto count_v = values_v.size(); + +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; + +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; + +template +constexpr auto names(std::index_sequence) noexcept { + constexpr auto names = std::array{{enum_name_v[I]>...}}; + return names; +} + +template +inline constexpr auto names_v = names(std::make_index_sequence>{}); + +template > +using names_t = decltype((names_v)); + +template +constexpr auto entries(std::index_sequence) noexcept { + constexpr auto entries = std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; + return entries; +} + +template +inline constexpr auto entries_v = entries(std::make_index_sequence>{}); + +template > +using entries_t = decltype((entries_v)); + +template > +constexpr bool is_sparse() noexcept { + if constexpr (count_v == 0) { + return false; + } else if constexpr (std::is_same_v) { // bool special case + return false; + } else { + constexpr auto max = (S == enum_subtype::flags) ? log2(max_v) : max_v; + constexpr auto min = (S == enum_subtype::flags) ? log2(min_v) : min_v; + constexpr auto range_size = max - min + 1; + + return range_size != count_v; + } +} + +template > +inline constexpr bool is_sparse_v = is_sparse(); + +template +struct is_reflected +#if defined(MAGIC_ENUM_NO_CHECK_REFLECTED_ENUM) + : std::true_type {}; +#else + : std::bool_constant && (count_v != 0)> {}; +#endif + +template +inline constexpr bool is_reflected_v = is_reflected, S>{}; + +template +struct enable_if_enum {}; + +template +struct enable_if_enum { + using type = R; + static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); +}; + +template , typename D = std::decay_t> +using enable_if_t = typename enable_if_enum && std::is_invocable_r_v, R>::type; + +template >, int> = 0> +using enum_concept = T; + +template > +struct is_scoped_enum : std::false_type {}; + +template +struct is_scoped_enum : std::bool_constant>> {}; + +template > +struct is_unscoped_enum : std::false_type {}; + +template +struct is_unscoped_enum : std::bool_constant>> {}; + +template >> +struct underlying_type {}; + +template +struct underlying_type : std::underlying_type> {}; + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + +template +struct constexpr_hash_t; + +template +struct constexpr_hash_t>> { + constexpr auto operator()(Value value) const noexcept { + using U = typename underlying_type::type; + if constexpr (std::is_same_v) { // bool special case + return static_cast(value); + } else { + return static_cast(value); + } + } + using secondary_hash = constexpr_hash_t; +}; + +template +struct constexpr_hash_t>> { + static constexpr std::uint32_t crc_table[256] { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL + }; + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto crc = static_cast(0xffffffffL); + for (const auto c : value) { + crc = (crc >> 8) ^ crc_table[(crc ^ static_cast(c)) & 0xff]; + } + return crc ^ 0xffffffffL; + } + + struct secondary_hash { + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto acc = static_cast(2166136261ULL); + for (const auto c : value) { + acc = ((acc ^ static_cast(c)) * static_cast(16777619ULL)) & (std::numeric_limits::max)(); + } + return static_cast(acc); + } + }; +}; + +template +inline constexpr Hash hash_v{}; + +template +constexpr auto calculate_cases(std::size_t Page) noexcept { + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + + using switch_t = std::invoke_result_t; + static_assert(std::is_integral_v && !std::is_same_v); + const std::size_t values_to = (std::min)(static_cast(256), size - Page); + + std::array result{}; + auto fill = result.begin(); + { + auto first = values.begin() + static_cast(Page); + auto last = values.begin() + static_cast(Page + values_to); + while (first != last) { + *fill++ = hash_v(*first++); + } + } + + // dead cases, try to avoid case collisions + for (switch_t last_value = result[values_to - 1]; fill != result.end() && last_value != (std::numeric_limits::max)(); *fill++ = ++last_value) { + } + + { + auto it = result.begin(); + auto last_value = (std::numeric_limits::min)(); + for (; fill != result.end(); *fill++ = last_value++) { + while (last_value == *it) { + ++last_value, ++it; + } + } + } + + return result; +} + +template +constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v) { + if constexpr (std::is_void_v) { + std::forward(f)(std::forward(args)...); + } else { + return static_cast(std::forward(f)(std::forward(args)...)); + } +} + +enum class case_call_t { + index, + value +}; + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr bool has_duplicate() noexcept { + using value_t = std::decay_t; + using hash_value_t = std::invoke_result_t; + std::arraysize()> hashes{}; + std::size_t size = 0; + for (auto elem : *Arr) { + hashes[size] = hash_v(elem); + for (auto i = size++; i > 0; --i) { + if (hashes[i] < hashes[i - 1]) { + auto tmp = hashes[i]; + hashes[i] = hashes[i - 1]; + hashes[i - 1] = tmp; + } else if (hashes[i] == hashes[i - 1]) { + return false; + } else { + break; + } + } + } + return true; +} + +#define MAGIC_ENUM_CASE(val) \ + case cases[val]: \ + if constexpr ((val) + Page < size) { \ + if (!pred(values[val + Page], searched)) { \ + break; \ + } \ + if constexpr (CallValue == case_call_t::index) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ + } else if constexpr (std::is_invocable_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } else if constexpr (CallValue == case_call_t::value) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), enum_constant{}); \ + } else if constexpr (std::is_invocable_r_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } \ + break; \ + } else [[fallthrough]]; + +template ::value_type>, + typename BinaryPredicate = std::equal_to<>, + typename Lambda, + typename ResultGetterType> +constexpr decltype(auto) constexpr_switch( + Lambda&& lambda, + typename std::decay_t::value_type searched, + ResultGetterType&& def, + BinaryPredicate&& pred = {}) { + using result_t = std::invoke_result_t; + using hash_t = std::conditional_t(), Hash, typename Hash::secondary_hash>; + static_assert(has_duplicate(), "magic_enum::detail::constexpr_switch duplicated hash found, please report it: https://github.com/Neargye/magic_enum/issues."); + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + constexpr std::array cases = calculate_cases(Page); + + switch (hash_v(searched)) { + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_CASE) + default: + if constexpr (size > 256 + Page) { + return constexpr_switch(std::forward(lambda), searched, std::forward(def)); + } + break; + } + return def(); +} + +#undef MAGIC_ENUM_CASE + +#endif + +} // namespace magic_enum::detail + +// Checks is magic_enum supported compiler. +inline constexpr bool is_magic_enum_supported = detail::supported::value; + +template +using Enum = detail::enum_concept; + +// Checks whether T is an Unscoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) type. Otherwise, value is equal to false. +template +struct is_unscoped_enum : detail::is_unscoped_enum {}; + +template +inline constexpr bool is_unscoped_enum_v = is_unscoped_enum::value; + +// Checks whether T is an Scoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) type. Otherwise, value is equal to false. +template +struct is_scoped_enum : detail::is_scoped_enum {}; + +template +inline constexpr bool is_scoped_enum_v = is_scoped_enum::value; + +// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T. +// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed. +template +struct underlying_type : detail::underlying_type {}; + +template +using underlying_type_t = typename underlying_type::type; + +template +using enum_constant = detail::enum_constant; + +// Returns type name of enum. +template +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::type_name_v>; + static_assert(!name.empty(), "magic_enum::enum_type_name enum type does not have a name."); + + return name; +} + +// Returns number of enum values. +template > +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_t { + return detail::count_v, S>; +} + +// Returns enum value at specified index. +// No bounds checking is performed: the behavior is undefined if index >= number of enum values. +template > +[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v) { + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::values_v[index]; + } else { + constexpr auto min = (S == detail::enum_subtype::flags) ? detail::log2(detail::min_v) : detail::min_v; + + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::value(index); + } +} + +// Returns enum value at specified index. +template > +[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + static_assert(I < detail::count_v, "magic_enum::enum_value out of range."); + + return enum_value(I); +} + +// Returns std::array with enum values, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::values_v; +} + +// Returns integer value from enum value. +template +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); +} + +// Returns underlying value from enum value. +template +[[nodiscard]] constexpr auto enum_underlying(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); +} + +// Obtains index in enum values from enum value. +// Returns optional with index. +template > +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + using U = underlying_type_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{i}; }, + value, + detail::default_result_type_lambda>); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (enum_value(i) == value) { + return i; + } + } + return {}; // Invalid value or out of range. +#endif + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return static_cast(v - detail::min_v); + } + return {}; // Invalid value or out of range. + } +} + +// Obtains index in enum values from enum value. +// Returns optional with index. +template +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return enum_index(value); +} + +// Obtains index in enum values from static storage enum variable. +template >> +[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t {\ + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + constexpr auto index = enum_index(V); + static_assert(index, "magic_enum::enum_index enum value does not have a index."); + + return *index; +} + +// Returns name from static storage enum variable. +// This version is much lighter on the compile times and is not restricted to the enum_range limitation. +template +[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::enum_name_v, V>; + static_assert(!name.empty(), "magic_enum::enum_name enum value does not have a name."); + + return name; +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template > +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if (const auto i = enum_index(value)) { + return detail::names_v[*i]; + } + return {}; +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template +[[nodiscard]] constexpr auto enum_name(E value) -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return enum_name(value); +} + +// Returns std::array with names, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::names_v; +} + +// Returns std::array with pairs (value, name), sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::entries_v; +} + +// Allows you to write magic_enum::enum_cast("bar", magic_enum::case_insensitive); +inline constexpr auto case_insensitive = detail::case_insensitive<>{}; + +// Obtains enum value from integer value. +// Returns optional with enum value. +template > +[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + [](D v) { return optional{v}; }, + static_cast(value), + detail::default_result_type_lambda>); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (value == static_cast>(enum_value(i))) { + return static_cast(value); + } + } + return {}; // Invalid value or out of range. +#endif + } else { + if (value >= detail::min_v && value <= detail::max_v) { + return static_cast(value); + } + return {}; // Invalid value or out of range. + } +} + +// Obtains enum value from name. +// Returns optional with enum value. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) + if constexpr (detail::is_default_predicate()) { + return detail::constexpr_switch<&detail::names_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{detail::values_v[i]}; }, + value, + detail::default_result_type_lambda>, + [&p](string_view lhs, string_view rhs) { return detail::cmp_equal(lhs, rhs, p); }); + } +#endif + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(value, detail::names_v[i], p)) { + return enum_value(i); + } + } + return {}; // Invalid value or out of range. +} + +// Checks whether enum contains value with such value. +template > +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_cast(static_cast(value))); +} + +// Checks whether enum contains value with such value. +template +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_cast(static_cast(value))); +} + +// Checks whether enum contains value with such integer value. +template > +[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_cast(value)); +} + +// Checks whether enum contains enumerator with such name. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_cast(value, std::move(p))); +} + +// Returns true if the enum integer value is in the range of values that can be reflected. +template > +[[nodiscard]] constexpr auto enum_reflected(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + if constexpr (detail::is_reflected_v) { + constexpr auto min = detail::reflected_min(); + constexpr auto max = detail::reflected_max(); + return value >= min && value <= max; + } else { + return false; + } +} + +// Returns true if the enum value is in the range of values that can be reflected. +template > +[[nodiscard]] constexpr auto enum_reflected(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return enum_reflected(static_cast>(value)); +} + +// Returns true if the enum value is in the range of values that can be reflected. +template +[[nodiscard]] constexpr auto enum_reflected(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return enum_reflected(value); +} + +template +inline constexpr auto as_flags = AsFlags ? detail::enum_subtype::flags : detail::enum_subtype::common; + +template +inline constexpr auto as_common = AsFlags ? detail::enum_subtype::common : detail::enum_subtype::flags; + +namespace bitwise_operators { + +template = 0> +constexpr E operator~(E rhs) noexcept { + return static_cast(~static_cast>(rhs)); +} + +template = 0> +constexpr E operator|(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} + +template = 0> +constexpr E operator&(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +template = 0> +constexpr E operator^(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); +} + +template = 0> +constexpr E& operator|=(E& lhs, E rhs) noexcept { + return lhs = (lhs | rhs); +} + +template = 0> +constexpr E& operator&=(E& lhs, E rhs) noexcept { + return lhs = (lhs & rhs); +} + +template = 0> +constexpr E& operator^=(E& lhs, E rhs) noexcept { + return lhs = (lhs ^ rhs); +} + +} // namespace magic_enum::bitwise_operators + +} // namespace magic_enum + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +#undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +#undef MAGIC_ENUM_VS_2017_WORKAROUND +#undef MAGIC_ENUM_ARRAY_CONSTEXPR +#undef MAGIC_ENUM_FOR_EACH_256 + +#endif // NEARGYE_MAGIC_ENUM_HPP diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_all.hpp b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_all.hpp new file mode 100644 index 0000000..7a05281 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_all.hpp @@ -0,0 +1,44 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_ALL_HPP +#define NEARGYE_MAGIC_ENUM_ALL_HPP + +#include "magic_enum.hpp" +#include "../magic_enum_containers.hpp" +#include "../magic_enum_flags.hpp" +#include "../magic_enum_format.hpp" +#include "../magic_enum_fuse.hpp" +#include "../magic_enum_iostream.hpp" +#include "../magic_enum_switch.hpp" +#include "../magic_enum_utility.hpp" + +#endif // NEARGYE_MAGIC_ENUM_ALL_HPP diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_containers.hpp b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_containers.hpp new file mode 100644 index 0000000..f817306 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_containers.hpp @@ -0,0 +1,1174 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// Copyright (c) 2022 - 2023 Bela Schaum . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_CONTAINERS_HPP +#define NEARGYE_MAGIC_ENUM_CONTAINERS_HPP + +#include "magic_enum.hpp" + +#if !defined(MAGIC_ENUM_NO_EXCEPTION) && (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +#endif +# define MAGIC_ENUM_THROW(...) throw (__VA_ARGS__) +#else +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +#endif +# define MAGIC_ENUM_THROW(...) std::abort() +#endif + +namespace magic_enum::containers { + +namespace detail { + +template +static constexpr bool is_transparent_v{}; + +template +static constexpr bool is_transparent_v>{true}; + +template , typename T1, typename T2> +constexpr bool equal(T1&& t1, T2&& t2, Eq&& eq = {}) { + auto first1 = t1.begin(); + auto last1 = t1.end(); + auto first2 = t2.begin(); + auto last2 = t2.end(); + + for (; first1 != last1; ++first1, ++first2) { + if (first2 == last2 || !eq(*first1, *first2)) { + return false; + } + } + return first2 == last2; +} + +template , typename T1, typename T2> +constexpr bool lexicographical_compare(T1&& t1, T2&& t2, Cmp&& cmp = {}) noexcept { + auto first1 = t1.begin(); + auto last1 = t1.end(); + auto first2 = t2.begin(); + auto last2 = t2.end(); + + // copied from std::lexicographical_compare + for (; (first1 != last1) && (first2 != last2); ++first1, (void)++first2) { + if (cmp(*first1, *first2)) { + return true; + } + if (cmp(*first2, *first1)) { + return false; + } + } + return (first1 == last1) && (first2 != last2); +} + +template +constexpr std::size_t popcount(T x) noexcept { + std::size_t c = 0; + while (x > 0) { + c += x & 1; + x >>= 1; + } + return c; +} + +template , typename ForwardIt, typename E> +constexpr ForwardIt lower_bound(ForwardIt first, ForwardIt last, E&& e, Cmp&& comp = {}) { + auto count = std::distance(first, last); + for (auto it = first; count > 0;) { + auto step = count / 2; + std::advance(it, step); + if (comp(*it, e)) { + first = ++it; + count -= step + 1; + } else { + count = step; + } + } + return first; +} + +template , typename BidirIt, typename E> +constexpr auto equal_range(BidirIt begin, BidirIt end, E&& e, Cmp&& comp = {}) { + const auto first = lower_bound(begin, end, e, comp); + return std::pair{first, lower_bound(std::make_reverse_iterator(end), std::make_reverse_iterator(first), e, [&comp](auto&& lhs, auto&& rhs) { return comp(rhs, lhs); }).base()}; +} + +template , typename = void> +class indexing { + [[nodiscard]] static constexpr auto get_indices() noexcept { + // reverse result index mapping + std::array()> rev_res{}; + + // std::iota + for (std::size_t i = 0; i < enum_count(); ++i) { + rev_res[i] = i; + } + + constexpr auto orig_values = enum_values(); + constexpr Cmp cmp{}; + + // ~std::sort + for (std::size_t i = 0; i < enum_count(); ++i) { + for (std::size_t j = i + 1; j < enum_count(); ++j) { + if (cmp(orig_values[rev_res[j]], orig_values[rev_res[i]])) { + auto tmp = rev_res[i]; + rev_res[i] = rev_res[j]; + rev_res[j] = tmp; + } + } + } + + std::array()> sorted_values{}; + // reverse the sorted indices + std::array()> res{}; + for (std::size_t i = 0; i < enum_count(); ++i) { + res[rev_res[i]] = i; + sorted_values[i] = orig_values[rev_res[i]]; + } + + return std::pair{sorted_values, res}; + } + + static constexpr auto indices = get_indices(); + + public: + [[nodiscard]] static constexpr const E* begin() noexcept { return indices.first.data(); } + + [[nodiscard]] static constexpr const E* end() noexcept { return indices.first.data() + indices.first.size(); } + + [[nodiscard]] static constexpr const E* it(std::size_t i) noexcept { return indices.first.data() + i; } + + [[nodiscard]] static constexpr optional at(E val) noexcept { + if (auto i = enum_index(val)) { + return indices.second[*i]; + } + return {}; + } +}; + +template +class indexing> && (std::is_same_v> || std::is_same_v>)>> { + static constexpr auto& values = enum_values(); + + public: + [[nodiscard]] static constexpr const E* begin() noexcept { return values.data(); } + + [[nodiscard]] static constexpr const E* end() noexcept { return values.data() + values.size(); } + + [[nodiscard]] static constexpr const E* it(std::size_t i) noexcept { return values.data() + i; } + + [[nodiscard]] static constexpr optional at(E val) noexcept { return enum_index(val); } +}; + +template +struct indexing { + using is_transparent = std::true_type; + + template + [[nodiscard]] static constexpr optional at(E val) noexcept { + return indexing::at(val); + } +}; + +template , typename = void> +struct name_sort_impl { + [[nodiscard]] constexpr bool operator()(E e1, E e2) const noexcept { return Cmp{}(enum_name(e1), enum_name(e2)); } +}; + +template +struct name_sort_impl { + using is_transparent = std::true_type; + + template + struct FullCmp : C {}; + + template + struct FullCmp && std::is_invocable_v>> { + [[nodiscard]] constexpr bool operator()(string_view s1, string_view s2) const noexcept { return lexicographical_compare(s1, s2); } + }; + + template + [[nodiscard]] constexpr std::enable_if_t< + // at least one of need to be an enum type + (std::is_enum_v> || std::is_enum_v>) && + // if both is enum, only accept if the same enum + (!std::is_enum_v> || !std::is_enum_v> || std::is_same_v) && + // is invocable with comparator + (std::is_invocable_r_v, std::conditional_t>, string_view, E1>, std::conditional_t>, string_view, E2>>), + bool> + operator()(E1 e1, E2 e2) const noexcept { + using D1 = std::decay_t; + using D2 = std::decay_t; + constexpr FullCmp<> cmp{}; + + if constexpr (std::is_enum_v && std::is_enum_v) { + return cmp(enum_name(e1), enum_name(e2)); + } else if constexpr (std::is_enum_v) { + return cmp(enum_name(e1), e2); + } else /* if constexpr (std::is_enum_v) */ { + return cmp(e1, enum_name(e2)); + } + } +}; + +struct raw_access_t {}; + +template +struct FilteredIterator { + Parent parent; + Iterator first; + Iterator last; + Iterator current; + Getter getter; + Predicate predicate; + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = std::remove_reference_t>; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + constexpr FilteredIterator() noexcept = default; + constexpr FilteredIterator(const FilteredIterator&) = default; + constexpr FilteredIterator& operator=(const FilteredIterator&) = default; + constexpr FilteredIterator(FilteredIterator&&) noexcept = default; + constexpr FilteredIterator& operator=(FilteredIterator&&) noexcept = default; + + template && std::is_convertible_v>*> + constexpr explicit FilteredIterator(const FilteredIterator& other) + : parent(other.parent), first(other.first), last(other.last), current(other.current), getter(other.getter), predicate(other.predicate) {} + + constexpr FilteredIterator(Parent p, Iterator begin, Iterator end, Iterator curr, Getter get = {}, Predicate pred = {}) + : parent(p), first(std::move(begin)), last(std::move(end)), current(std::move(curr)), getter{std::move(get)}, predicate{std::move(pred)} { + if (current == first && !predicate(parent, current)) { + ++*this; + } + } + + [[nodiscard]] constexpr reference operator*() const { return getter(parent, current); } + + [[nodiscard]] constexpr pointer operator->() const { return std::addressof(**this); } + + constexpr FilteredIterator& operator++() { + do { + ++current; + } while (current != last && !predicate(parent, current)); + return *this; + } + + [[nodiscard]] constexpr FilteredIterator operator++(int) { + FilteredIterator cp = *this; + ++*this; + return cp; + } + + constexpr FilteredIterator& operator--() { + do { + --current; + } while (current != first && !predicate(parent, current)); + return *this; + } + + [[nodiscard]] constexpr FilteredIterator operator--(int) { + FilteredIterator cp = *this; + --*this; + return cp; + } + + [[nodiscard]] friend constexpr bool operator==(const FilteredIterator& lhs, const FilteredIterator& rhs) { return lhs.current == rhs.current; } + + [[nodiscard]] friend constexpr bool operator!=(const FilteredIterator& lhs, const FilteredIterator& rhs) { return lhs.current != rhs.current; } +}; + +} // namespace detail + +template +using name_less = detail::name_sort_impl; + +template +using name_greater = detail::name_sort_impl>; + +using name_less_case_insensitive = detail::name_sort_impl>>; + +using name_greater_case_insensitive = detail::name_sort_impl>>; + +template +using default_indexing = detail::indexing; + +template > +using comparator_indexing = detail::indexing; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ARRAY // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +struct array { + static_assert(std::is_enum_v); + static_assert(std::is_trivially_constructible_v); + static_assert(enum_count() > 0 && Index::at(enum_values().front())); + + using index_type = Index; + using container_type = std::array()>; + + using value_type = typename container_type::value_type; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + using reference = typename container_type::reference; + using const_reference = typename container_type::const_reference; + using pointer = typename container_type::pointer; + using const_pointer = typename container_type::const_pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using reverse_iterator = typename container_type::reverse_iterator; + using const_reverse_iterator = typename container_type::const_reverse_iterator; + + constexpr reference at(E pos) { + if (auto index = index_type::at(pos)) { + return a[*index]; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::array::at Unrecognized position")); + } + + constexpr const_reference at(E pos) const { + if (auto index = index_type::at(pos)) { + return a[*index]; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::array::at: Unrecognized position")); + } + + [[nodiscard]] constexpr reference operator[](E pos) { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), a[*i]; + } + + [[nodiscard]] constexpr const_reference operator[](E pos) const { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), a[*i]; + } + + [[nodiscard]] constexpr reference front() noexcept { return a.front(); } + + [[nodiscard]] constexpr const_reference front() const noexcept { return a.front(); } + + [[nodiscard]] constexpr reference back() noexcept { return a.back(); } + + [[nodiscard]] constexpr const_reference back() const noexcept { return a.back(); } + + [[nodiscard]] constexpr pointer data() noexcept { return a.data(); } + + [[nodiscard]] constexpr const_pointer data() const noexcept { return a.data(); } + + [[nodiscard]] constexpr iterator begin() noexcept { return a.begin(); } + + [[nodiscard]] constexpr const_iterator begin() const noexcept { return a.begin(); } + + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return a.cbegin(); } + + [[nodiscard]] constexpr iterator end() noexcept { return a.end(); } + + [[nodiscard]] constexpr const_iterator end() const noexcept { return a.end(); } + + [[nodiscard]] constexpr const_iterator cend() const noexcept { return a.cend(); } + + [[nodiscard]] constexpr iterator rbegin() noexcept { return a.rbegin(); } + + [[nodiscard]] constexpr const_iterator rbegin() const noexcept { return a.rbegin(); } + + [[nodiscard]] constexpr const_iterator crbegin() const noexcept { return a.crbegin(); } + + [[nodiscard]] constexpr iterator rend() noexcept { return a.rend(); } + + [[nodiscard]] constexpr const_iterator rend() const noexcept { return a.rend(); } + + [[nodiscard]] constexpr const_iterator crend() const noexcept { return a.crend(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return a.empty(); } + + [[nodiscard]] constexpr size_type size() const noexcept { return a.size(); } + + [[nodiscard]] constexpr size_type max_size() const noexcept { return a.max_size(); } + + constexpr void fill(const V& value) { + for (auto& v : a) { + v = value; + } + } + + constexpr void swap(array& other) noexcept(std::is_nothrow_swappable_v) { + for (std::size_t i = 0; i < a.size(); ++i) { + auto v = std::move(other.a[i]); + other.a[i] = std::move(a[i]); + a[i] = std::move(v); + } + } + + [[nodiscard]] friend constexpr bool operator==(const array& a1, const array& a2) { return detail::equal(a1, a2); } + + [[nodiscard]] friend constexpr bool operator!=(const array& a1, const array& a2) { return !detail::equal(a1, a2); } + + [[nodiscard]] friend constexpr bool operator<(const array& a1, const array& a2) { return detail::lexicographical_compare(a1, a2); } + + [[nodiscard]] friend constexpr bool operator<=(const array& a1, const array& a2) { return !detail::lexicographical_compare(a2, a1); } + + [[nodiscard]] friend constexpr bool operator>(const array& a1, const array& a2) { return detail::lexicographical_compare(a2, a1); } + + [[nodiscard]] friend constexpr bool operator>=(const array& a1, const array& a2) { return !detail::lexicographical_compare(a1, a2); } + + container_type a; +}; + +namespace detail { + +template +constexpr array> to_array_impl(T (&a)[N], std::index_sequence) { + return {{a[I]...}}; +} + +template +constexpr array> to_array_impl(T(&&a)[N], std::index_sequence) { + return {{std::move(a[I])...}}; +} + +} // namespace detail + +template +constexpr std::enable_if_t<(enum_count() == N), array>> to_array(T (&a)[N]) { + return detail::to_array_impl(a, std::make_index_sequence{}); +} + +template +constexpr std::enable_if_t<(enum_count() == N), array>> to_array(T(&&a)[N]) { + return detail::to_array_impl(std::move(a), std::make_index_sequence{}); +} + +template +constexpr std::enable_if_t<(enum_count() == sizeof...(Ts)), array>>> make_array(Ts&&... ts) { + return {{std::forward(ts)...}}; +} + +inline constexpr detail::raw_access_t raw_access{}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// BITSET // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +class bitset { + static_assert(std::is_enum_v); + static_assert(std::is_trivially_constructible_v); + static_assert(enum_count() > 0 && Index::at(enum_values().front())); + + using base_type = std::conditional_t() <= 8, std::uint_least8_t, + std::conditional_t() <= 16, std::uint_least16_t, + std::conditional_t() <= 32, std::uint_least32_t, + std::uint_least64_t>>>; + + static constexpr std::size_t bits_per_base = sizeof(base_type) * 8; + static constexpr std::size_t base_type_count = (enum_count() > 0 ? (enum_count() - 1) / bits_per_base + 1 : 0); + static constexpr std::size_t not_interested = base_type_count * bits_per_base - enum_count(); + static constexpr base_type last_value_max = (base_type{1} << (bits_per_base - not_interested)) - 1; + + template + class reference_impl { + friend class bitset; + + parent_t parent; + std::size_t num_index; + base_type bit_index; + + constexpr reference_impl(parent_t p, std::size_t i) noexcept : reference_impl(p, std::pair{i / bits_per_base, base_type{1} << (i % bits_per_base)}) {} + + constexpr reference_impl(parent_t p, std::pair i) noexcept : parent(p), num_index(std::get<0>(i)), bit_index(std::get<1>(i)) {} + + public: + constexpr reference_impl& operator=(bool v) noexcept { + if (v) { + parent->a[num_index] |= bit_index; + } else { + parent->a[num_index] &= ~bit_index; + } + return *this; + } + + constexpr reference_impl& operator=(const reference_impl& v) noexcept { + if (this == &v) { + return *this; + } + *this = static_cast(v); + return *this; + } + + [[nodiscard]] constexpr operator bool() const noexcept { return (parent->a[num_index] & bit_index) > 0; } + + [[nodiscard]] constexpr bool operator~() const noexcept { return !static_cast(*this); } + + constexpr reference_impl& flip() noexcept { + *this = ~*this; + return *this; + } + }; + + template + [[nodiscard]] constexpr T to_(detail::raw_access_t) const { + T res{}; + T flag{1}; + for (std::size_t i = 0; i < size(); ++i, flag <<= 1) { + if (const_reference{this, i}) { + if (i >= sizeof(T) * 8) { + MAGIC_ENUM_THROW(std::overflow_error("magic_enum::containers::bitset::to: Cannot represent enum in this type")); + } + res |= flag; + } + } + return res; + } + + public: + using index_type = Index; + using container_type = std::array; + using reference = reference_impl<>; + using const_reference = reference_impl; + + constexpr explicit bitset(detail::raw_access_t = raw_access) noexcept : a{{}} {} + + constexpr explicit bitset(detail::raw_access_t, unsigned long long val) : a{{}} { + unsigned long long bit{1}; + for (std::size_t i = 0; i < (sizeof(val) * 8); ++i, bit <<= 1) { + if ((val & bit) > 0) { + if (i >= enum_count()) { + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::constructor: Upper bit set in raw number")); + } + + reference{this, i} = true; + } + } + } + + constexpr explicit bitset(detail::raw_access_t, string_view sv, string_view::size_type pos = 0, string_view::size_type n = string_view::npos, char_type zero = static_cast('0'), char_type one = static_cast('1')) + : a{{}} { + std::size_t i = 0; + for (auto c : sv.substr(pos, n)) { + if (c == one) { + if (i >= enum_count()) { + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::constructor: Upper bit set in raw string")); + } + reference{this, i} = true; + } else if (c != zero) { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized character in raw string")); + } + ++i; + } + } + + constexpr explicit bitset(detail::raw_access_t, const char_type* str, std::size_t n = ~std::size_t{0}, char_type zero = static_cast('0'), char_type one = static_cast('1')) + : bitset(string_view{str, (std::min)(std::char_traits::length(str), n)}, 0, n, zero, one) {} + + constexpr bitset(std::initializer_list starters) : a{{}} { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + for (auto& f : starters) { + *this |= bitset(f); + } + } else { + for (auto& f : starters) { + set(f); + } + } + } + template && magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags, int> = 0> + constexpr explicit bitset(V starter) : a{{}} { + auto u = enum_underlying(starter); + for (E v : enum_values()) { + if (auto ul = enum_underlying(v); (ul & u) != 0) { + u &= ~ul; + (*this)[v] = true; + } + } + if (u != 0) { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in flag")); + } + } + + template > + constexpr explicit bitset(string_view sv, Cmp&& cmp = {}, char_type sep = static_cast('|')) { + for (std::size_t to = 0; (to = magic_enum::detail::find(sv, sep)) != string_view::npos; sv.remove_prefix(to + 1)) { + if (auto v = enum_cast(sv.substr(0, to), cmp)) { + set(*v); + } else { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in string")); + } + } + if (!sv.empty()) { + if (auto v = enum_cast(sv, cmp)) { + set(*v); + } else { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in string")); + } + } + } + + [[nodiscard]] friend constexpr bool operator==(const bitset& lhs, const bitset& rhs) noexcept { return detail::equal(lhs.a, rhs.a); } + + [[nodiscard]] friend constexpr bool operator!=(const bitset& lhs, const bitset& rhs) noexcept { return !detail::equal(lhs.a, rhs.a); } + + [[nodiscard]] constexpr bool operator[](E pos) const { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), static_cast(const_reference(this, *i)); + } + + [[nodiscard]] constexpr reference operator[](E pos) { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), reference{this, *i}; + } + + constexpr bool test(E pos) const { + if (auto i = index_type::at(pos)) { + return static_cast(const_reference(this, *i)); + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::test: Unrecognized position")); + } + + [[nodiscard]] constexpr bool all() const noexcept { + if constexpr (base_type_count == 0) { + return true; + } + + for (std::size_t i = 0; i < base_type_count - (not_interested > 0); ++i) { + auto check = ~a[i]; + if (check) { + return false; + } + } + + if constexpr (not_interested > 0) { + return a[base_type_count - 1] == last_value_max; + } + } + + [[nodiscard]] constexpr bool any() const noexcept { + for (auto& v : a) { + if (v > 0) { + return true; + } + } + return false; + } + + [[nodiscard]] constexpr bool none() const noexcept { return !any(); } + + [[nodiscard]] constexpr std::size_t count() const noexcept { + std::size_t c = 0; + for (auto& v : a) { + c += detail::popcount(v); + } + return c; + } + + [[nodiscard]] constexpr std::size_t size() const noexcept { return enum_count(); } + + [[nodiscard]] constexpr std::size_t max_size() const noexcept { return enum_count(); } + + constexpr bitset& operator&=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] &= other.a[i]; + } + return *this; + } + + constexpr bitset& operator|=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] |= other.a[i]; + } + return *this; + } + + constexpr bitset& operator^=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] ^= other.a[i]; + } + return *this; + } + + [[nodiscard]] constexpr bitset operator~() const noexcept { + bitset res; + for (std::size_t i = 0; i < base_type_count - (not_interested > 0); ++i) { + res.a[i] = ~a[i]; + } + + if constexpr (not_interested > 0) { + res.a[base_type_count - 1] = ~a[base_type_count - 1] & last_value_max; + } + return res; + } + + constexpr bitset& set() noexcept { + for (std::size_t i = 0; i < base_type_count - (not_interested > 0); ++i) { + a[i] = ~base_type{0}; + } + + if constexpr (not_interested > 0) { + a[base_type_count - 1] = last_value_max; + } + return *this; + } + + constexpr bitset& set(E pos, bool value = true) { + if (auto i = index_type::at(pos)) { + reference{this, *i} = value; + return *this; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::set: Unrecognized position")); + } + + constexpr bitset& reset() noexcept { return *this = bitset{}; } + + constexpr bitset& reset(E pos) { + if (auto i = index_type::at(pos)) { + reference{this, *i} = false; + return *this; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::reset: Unrecognized position")); + } + + constexpr bitset& flip() noexcept { return *this = ~*this; } + + [[nodiscard]] friend constexpr bitset operator&(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp &= rhs; + return cp; + } + + [[nodiscard]] friend constexpr bitset operator|(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp |= rhs; + return cp; + } + + [[nodiscard]] friend constexpr bitset operator^(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp ^= rhs; + return cp; + } + + template + [[nodiscard]] constexpr explicit operator std::enable_if_t == magic_enum::detail::enum_subtype::flags, E>() const { + E res{}; + for (const auto& e : enum_values()) { + if (test(e)) { + res |= e; + } + } + return res; + } + + [[nodiscard]] string to_string(char_type sep = static_cast('|')) const { + string name; + + for (const auto& e : enum_values()) { + if (test(e)) { + if (!name.empty()) { + name.append(1, sep); + } + auto n = enum_name(e); + name.append(n.data(), n.size()); + } + } + return name; + } + + [[nodiscard]] string to_string(detail::raw_access_t, char_type zero = static_cast('0'), char_type one = static_cast('1')) const { + string name; + name.reserve(size()); + for (std::size_t i = 0; i < size(); ++i) { + name.append(1, const_reference{this, i} ? one : zero); + } + return name; + } + + [[nodiscard]] constexpr unsigned long long to_ullong(detail::raw_access_t raw) const { return to_(raw); } + + [[nodiscard]] constexpr unsigned long long to_ulong(detail::raw_access_t raw) const { return to_(raw); } + + friend std::ostream& operator<<(std::ostream& o, const bitset& bs) { return o << bs.to_string(); } + + friend std::istream& operator>>(std::istream& i, bitset& bs) { + string s; + if (i >> s; !s.empty()) { + bs = bitset(string_view{s}); + } + return i; + } + + private: + container_type a; +}; + +template +explicit bitset(V starter) -> bitset; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SET // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +class set { + using index_type = detail::indexing; + struct Getter { + constexpr const E& operator()(const set*, const E* p) const noexcept { return *p; } + }; + struct Predicate { + constexpr bool operator()(const set* h, const E* e) const noexcept { return h->a[*e]; } + }; + + public: + using container_type = bitset; + using key_type = E; + using value_type = E; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using key_compare = Cmp; + using value_compare = Cmp; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = detail::FilteredIterator; + using const_iterator = detail::FilteredIterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr set() noexcept = default; + + template + constexpr set(InputIt first, InputIt last) { + while (first != last) { + insert(*first++); + } + } + + constexpr set(std::initializer_list ilist) { + for (auto e : ilist) { + insert(e); + } + } + template && magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags, int> = 0> + constexpr explicit set(V starter) { + auto u = enum_underlying(starter); + for (E v : enum_values()) { + if ((enum_underlying(v) & u) != 0) { + insert(v); + } + } + } + + constexpr set(const set&) noexcept = default; + constexpr set(set&&) noexcept = default; + + constexpr set& operator=(const set&) noexcept = default; + constexpr set& operator=(set&&) noexcept = default; + constexpr set& operator=(std::initializer_list ilist) { + for (auto e : ilist) { + insert(e); + } + } + + constexpr const_iterator begin() const noexcept { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::begin()}; + } + + constexpr const_iterator end() const noexcept { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::end()}; + } + + constexpr const_iterator cbegin() const noexcept { return begin(); } + + constexpr const_iterator cend() const noexcept { return end(); } + + constexpr const_reverse_iterator rbegin() const noexcept { return {end()}; } + + constexpr const_reverse_iterator rend() const noexcept { return {begin()}; } + + constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + + constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return s == 0; } + + [[nodiscard]] constexpr size_type size() const noexcept { return s; } + + [[nodiscard]] constexpr size_type max_size() const noexcept { return a.max_size(); } + + constexpr void clear() noexcept { + a.reset(); + s = 0; + } + + constexpr std::pair insert(const value_type& value) noexcept { + if (auto i = index_type::at(value)) { + typename container_type::reference ref = a[value]; + bool r = !ref; + if (r) { + ref = true; + ++s; + } + + return {iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}, r}; + } + return {end(), false}; + } + + constexpr std::pair insert(value_type&& value) noexcept { return insert(value); } + + constexpr iterator insert(const_iterator, const value_type& value) noexcept { return insert(value).first; } + + constexpr iterator insert(const_iterator hint, value_type&& value) noexcept { return insert(hint, value); } + + template + constexpr void insert(InputIt first, InputIt last) noexcept { + while (first != last) { + insert(*first++); + } + } + + constexpr void insert(std::initializer_list ilist) noexcept { + for (auto v : ilist) { + insert(v); + } + } + + template + constexpr std::pair emplace(Args&&... args) noexcept { + return insert({std::forward(args)...}); + } + + template + constexpr iterator emplace_hint(const_iterator, Args&&... args) noexcept { + return emplace(std::forward(args)...).first; + } + + constexpr iterator erase(const_iterator pos) noexcept { + erase(*pos++); + return pos; + } + + constexpr iterator erase(const_iterator first, const_iterator last) noexcept { + while ((first = erase(first)) != last) { + } + return first; + } + + constexpr size_type erase(const key_type& key) noexcept { + typename container_type::reference ref = a[key]; + bool res = ref; + if (res) { + --s; + } + ref = false; + return res; + } + + template + constexpr std::enable_if_t, size_type> erase(K&& x) noexcept { + size_type c = 0; + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last;) { + c += erase(*first++); + } + return c; + } + + void swap(set& other) noexcept { + set cp = *this; + *this = other; + other = cp; + } + + [[nodiscard]] constexpr size_type count(const key_type& key) const noexcept { return index_type::at(key) && a[key]; } + + template + [[nodiscard]] constexpr std::enable_if_t, size_type> count(const K& x) const { + size_type c = 0; + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last; ++first) { + c += count(*first); + } + return c; + } + + [[nodiscard]] constexpr const_iterator find(const key_type& key) const noexcept { + if (auto i = index_type::at(key); i && a.test(key)) { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}; + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> find(const K& x) const { + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last; ++first) { + if (a.test(*first)) { + return find(*first); + } + } + return end(); + } + + [[nodiscard]] constexpr bool contains(const key_type& key) const noexcept { return count(key); } + + template + [[nodiscard]] constexpr std::enable_if_t, bool> contains(const K& x) const noexcept { + return count(x) > 0; + } + + [[nodiscard]] constexpr std::pair equal_range(const key_type& key) const noexcept { return {lower_bound(key), upper_bound(key)}; } + + template + [[nodiscard]] constexpr std::enable_if_t, std::pair> equal_range(const K& x) const noexcept { + return {lower_bound(x), upper_bound(x)}; + } + + [[nodiscard]] constexpr const_iterator lower_bound(const key_type& key) const noexcept { + if (auto i = index_type::at(key)) { + auto it = const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}; + return a.test(key) ? it : std::next(it); + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> lower_bound(const K& x) const noexcept { + auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); + return first != last ? lower_bound(*first) : end(); + } + + [[nodiscard]] constexpr const_iterator upper_bound(const key_type& key) const noexcept { + if (auto i = index_type::at(key)) { + return std::next(const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}); + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> upper_bound(const K& x) const noexcept { + auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); + return first != last ? upper_bound(*std::prev(last)) : end(); + } + + [[nodiscard]] constexpr key_compare key_comp() const { return {}; } + + [[nodiscard]] constexpr value_compare value_comp() const { return {}; } + + [[nodiscard]] constexpr friend bool operator==(const set& lhs, const set& rhs) noexcept { return lhs.a == rhs.a; } + + [[nodiscard]] constexpr friend bool operator!=(const set& lhs, const set& rhs) noexcept { return lhs.a != rhs.a; } + + [[nodiscard]] constexpr friend bool operator<(const set& lhs, const set& rhs) noexcept { + if (lhs.s < rhs.s) { + return true; + } + if (rhs.s < lhs.s) { + return false; + } + + for (auto it = index_type::begin(); it != index_type::end(); ++it) { + if (auto c = rhs.contains(*it); c != lhs.contains(*it)) { + return c; + } + } + return false; + } + + [[nodiscard]] constexpr friend bool operator<=(const set& lhs, const set& rhs) noexcept { return !(rhs < lhs); } + + [[nodiscard]] constexpr friend bool operator>(const set& lhs, const set& rhs) noexcept { return rhs < lhs; } + + [[nodiscard]] constexpr friend bool operator>=(const set& lhs, const set& rhs) noexcept { return !(lhs < rhs); } + + template + size_type erase_if(Pred pred) { + auto old_size = size(); + for (auto i = begin(), last = end(); i != last;) { + if (pred(*i)) { + i = erase(i); + } else { + ++i; + } + } + return old_size - size(); + } + + private: + container_type a; + std::size_t s = 0; +}; + +template +explicit set(V starter) -> set; + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), V&> get(array& a) noexcept { + return a.a[I]; +} + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), V&&> get(array&& a) noexcept { + return std::move(a.a[I]); +} + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), const V&> get(const array& a) noexcept { + return a.a[I]; +} + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), const V&&> get(const array&& a) noexcept { + return std::move(a.a[I]); +} + +template +constexpr std::enable_if_t && enum_contains(Enum), V&> get(array& a) { + return a[Enum]; +} + +template +constexpr std::enable_if_t && enum_contains(Enum), V&&> get(array&& a) { + return std::move(a[Enum]); +} + +template +constexpr std::enable_if_t && enum_contains(Enum), const V&> get(const array& a) { + return a[Enum]; +} + +template +constexpr std::enable_if_t && enum_contains(Enum), const V&&> get(const array&& a) { + return std::move(a[Enum]); +} + +} // namespace magic_enum::containers + +#endif // NEARGYE_MAGIC_ENUM_CONTAINERS_HPP diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_flags.hpp b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_flags.hpp new file mode 100644 index 0000000..f2d81b5 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_flags.hpp @@ -0,0 +1,222 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_FLAGS_HPP +#define NEARGYE_MAGIC_ENUM_FLAGS_HPP + +#include "magic_enum.hpp" + +#if defined(__clang__) +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. +#elif defined(_MSC_VER) +# pragma warning(push) +#endif + +namespace magic_enum { + +namespace detail { + +template > +constexpr U values_ors() noexcept { + static_assert(S == enum_subtype::flags, "magic_enum::detail::values_ors requires valid subtype."); + + auto ors = U{0}; + for (std::size_t i = 0; i < count_v; ++i) { + ors |= static_cast(values_v[i]); + } + + return ors; +} + +} // namespace magic_enum::detail + +// Returns name from enum-flags value. +// If enum-flags value does not have name or value out of range, returns empty string. +template +[[nodiscard]] auto enum_flags_name(E value, char_type sep = static_cast('|')) -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + string name; + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(enum_value(i)); (static_cast(value) & v) != 0) { + if (const auto n = detail::names_v[i]; !n.empty()) { + check_value |= v; + if (!name.empty()) { + name.append(1, sep); + } + name.append(n.data(), n.size()); + } else { + return {}; // Value out of range. + } + } + } + + if (check_value != 0 && check_value == static_cast(value)) { + return name; + } + return {}; // Invalid value or out of range. +} + +// Obtains enum-flags value from integer value. +// Returns optional with enum-flags value. +template +[[nodiscard]] constexpr auto enum_flags_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + using U = underlying_type_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::count_v == 0) { + static_cast(value); + return {}; // Empty enum. + } else { + if constexpr (detail::is_sparse_v) { + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(enum_value(i)); (value & v) != 0) { + check_value |= v; + } + } + + if (check_value != 0 && check_value == value) { + return static_cast(value); + } + } else { + constexpr auto min = detail::min_v; + constexpr auto max = detail::values_ors(); + + if (value >= min && value <= max) { + return static_cast(value); + } + } + return {}; // Invalid value or out of range. + } +} + +// Obtains enum-flags value from name. +// Returns optional with enum-flags value. +template > +[[nodiscard]] constexpr auto enum_flags_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { + using D = std::decay_t; + using U = underlying_type_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::count_v == 0) { + static_cast(value); + return {}; // Empty enum. + } else { + auto result = U{0}; + while (!value.empty()) { + const auto d = detail::find(value, '|'); + const auto s = (d == string_view::npos) ? value : value.substr(0, d); + auto f = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(s, detail::names_v[i], p)) { + f = static_cast(enum_value(i)); + result |= f; + break; + } + } + if (f == U{0}) { + return {}; // Invalid value or out of range. + } + value.remove_prefix((d == string_view::npos) ? value.size() : d + 1); + } + + if (result != U{0}) { + return static_cast(result); + } + return {}; // Invalid value or out of range. + } +} + +// Checks whether enum-flags contains value with such value. +template +[[nodiscard]] constexpr auto enum_flags_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_flags_cast(static_cast(value))); +} + +// Checks whether enum-flags contains value with such integer value. +template +[[nodiscard]] constexpr auto enum_flags_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_flags_cast(value)); +} + +// Checks whether enum-flags contains enumerator with such name. +template > +[[nodiscard]] constexpr auto enum_flags_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_flags_cast(value, std::move(p))); +} + +// Checks whether `flags set` contains `flag`. +// Note: If `flag` equals 0, it returns false, as 0 is not a flag. +template +constexpr auto enum_flags_test(E flags, E flag) noexcept -> detail::enable_if_t { + using U = underlying_type_t; + + return static_cast(flag) && ((static_cast(flags) & static_cast(flag)) == static_cast(flag)); +} + +// Checks whether `lhs flags set` and `rhs flags set` have common flags. +// Note: If `lhs flags set` or `rhs flags set` equals 0, it returns false, as 0 is not a flag, and therfore cannot have any matching flag. +template +constexpr auto enum_flags_test_any(E lhs, E rhs) noexcept -> detail::enable_if_t { + using U = underlying_type_t; + + return (static_cast(lhs) & static_cast(rhs)) != 0; +} + +} // namespace magic_enum + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // NEARGYE_MAGIC_ENUM_FLAGS_HPP diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_format.hpp b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_format.hpp new file mode 100644 index 0000000..f10371f --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_format.hpp @@ -0,0 +1,114 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_FORMAT_HPP +#define NEARGYE_MAGIC_ENUM_FORMAT_HPP + +#include "magic_enum.hpp" +#include "magic_enum_flags.hpp" + +#if !defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT) +# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT 1 +# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE +#endif + +namespace magic_enum::customize { + // customize enum to enable/disable automatic std::format + template + constexpr bool enum_format_enabled() noexcept { + return MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT; + } +} // magic_enum::customize + +#if defined(__cpp_lib_format) + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#include +#endif + +template +struct std::formatter> && magic_enum::customize::enum_format_enabled(), char>> : std::formatter { + template + auto format(E e, FormatContext& ctx) const { + static_assert(std::is_same_v, "formatter requires string_view::value_type type same as char."); + using D = std::decay_t; + + if constexpr (magic_enum::detail::supported::value) { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + if (const auto name = magic_enum::enum_flags_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } else { + if (const auto name = magic_enum::enum_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } + } + return formatter::format(std::to_string(magic_enum::enum_integer(e)), ctx); + } +}; + +#endif + +#if defined(FMT_VERSION) + +#include +struct fmt::formatter> && magic_enum::customize::enum_format_enabled(), char>> : fmt::formatter { + template + auto format(E e, FormatContext& ctx) const { + static_assert(std::is_same_v, "formatter requires string_view::value_type type same as char."); + using D = std::decay_t; + + if constexpr (magic_enum::detail::supported::value) { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + if (const auto name = magic_enum::enum_flags_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } else { + if (const auto name = magic_enum::enum_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } + } + return formatter::format(std::to_string(magic_enum::enum_integer(e)), ctx); + } +}; + +#endif + +#if defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE) +# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT +# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE +#endif + +#endif // NEARGYE_MAGIC_ENUM_FORMAT_HPP diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_fuse.hpp b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_fuse.hpp new file mode 100644 index 0000000..6b2a3ef --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_fuse.hpp @@ -0,0 +1,89 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_FUSE_HPP +#define NEARGYE_MAGIC_ENUM_FUSE_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +template +constexpr optional fuse_one_enum(optional hash, E value) noexcept { + if (hash) { + if (const auto index = enum_index(value)) { + return (*hash << log2((enum_count() << 1) - 1)) | *index; + } + } + return {}; +} + +template +constexpr optional fuse_enum(E value) noexcept { + return fuse_one_enum(0, value); +} + +template +constexpr optional fuse_enum(E head, Es... tail) noexcept { + return fuse_one_enum(fuse_enum(tail...), head); +} + +template +constexpr auto typesafe_fuse_enum(Es... values) noexcept { + enum class enum_fuse_t : std::uintmax_t; + const auto fuse = fuse_enum(values...); + if (fuse) { + return optional{static_cast(*fuse)}; + } + return optional{}; +} + +} // namespace magic_enum::detail + +// Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements. +template +[[nodiscard]] constexpr auto enum_fuse(Es... values) noexcept { + static_assert((std::is_enum_v> && ...), "magic_enum::enum_fuse requires enum type."); + static_assert(sizeof...(Es) >= 2, "magic_enum::enum_fuse requires at least 2 values."); + static_assert((detail::log2(enum_count>() + 1) + ...) <= (sizeof(std::uintmax_t) * 8), "magic_enum::enum_fuse does not work for large enums"); +#if defined(MAGIC_ENUM_NO_TYPESAFE_ENUM_FUSE) + const auto fuse = detail::fuse_enum...>(values...); +#else + const auto fuse = detail::typesafe_fuse_enum...>(values...); +#endif + return MAGIC_ENUM_ASSERT(fuse), fuse; +} + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_FUSE_HPP diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_iostream.hpp b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_iostream.hpp new file mode 100644 index 0000000..6130dcb --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_iostream.hpp @@ -0,0 +1,117 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_IOSTREAM_HPP +#define NEARGYE_MAGIC_ENUM_IOSTREAM_HPP + +#include "magic_enum.hpp" +#include "magic_enum_flags.hpp" + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#include +#endif + +namespace magic_enum { + +namespace ostream_operators { + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, E value) { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::supported::value) { + if constexpr (detail::subtype_v == detail::enum_subtype::flags) { + if (const auto name = enum_flags_name(value); !name.empty()) { + for (const auto c : name) { + os.put(c); + } + return os; + } + } else { + if (const auto name = enum_name(value); !name.empty()) { + for (const auto c : name) { + os.put(c); + } + return os; + } + } + } + return (os << static_cast(value)); +} + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { + return value ? (os << *value) : os; +} + +} // namespace magic_enum::ostream_operators + +namespace istream_operators { + +template = 0> +std::basic_istream& operator>>(std::basic_istream& is, E& value) { + using D = std::decay_t; + + std::basic_string s; + is >> s; + if constexpr (detail::supported::value) { + if constexpr (detail::subtype_v == detail::enum_subtype::flags) { + if (const auto v = enum_flags_cast(s)) { + value = *v; + } else { + is.setstate(std::basic_ios::failbit); + } + } else { + if (const auto v = enum_cast(s)) { + value = *v; + } else { + is.setstate(std::basic_ios::failbit); + } + } + } else { + is.setstate(std::basic_ios::failbit); + } + return is; +} + +} // namespace magic_enum::istream_operators + +namespace iostream_operators { + +using magic_enum::ostream_operators::operator<<; +using magic_enum::istream_operators::operator>>; + +} // namespace magic_enum::iostream_operators + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_IOSTREAM_HPP diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_switch.hpp b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_switch.hpp new file mode 100644 index 0000000..63fc19a --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_switch.hpp @@ -0,0 +1,195 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_SWITCH_HPP +#define NEARGYE_MAGIC_ENUM_SWITCH_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +struct default_result_type {}; + +template +struct identity { + using type = T; +}; + +struct nonesuch {}; + +template > +struct invoke_result : identity {}; + +template +struct invoke_result : std::invoke_result {}; + +template +using invoke_result_t = typename invoke_result::type; + +template +constexpr auto common_invocable(std::index_sequence) noexcept { + static_assert(std::is_enum_v, "magic_enum::detail::invocable_index requires enum type."); + + if constexpr (count_v == 0) { + return identity{}; + } else { + return std::common_type[I]>>...>{}; + } +} + +template +constexpr auto result_type() noexcept { + static_assert(std::is_enum_v, "magic_enum::detail::result_type requires enum type."); + + constexpr auto seq = std::make_index_sequence>{}; + using R = typename decltype(common_invocable(seq))::type; + if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { + return identity{}; + } else { + return identity{}; + } + } else { + if constexpr (std::is_convertible_v) { + return identity{}; + } else if constexpr (std::is_convertible_v) { + return identity{}; + } else { + return identity{}; + } + } +} + +template , typename R = typename decltype(result_type())::type> +using result_t = std::enable_if_t && !std::is_same_v, R>; + +#if !defined(MAGIC_ENUM_ENABLE_HASH) && !defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr decltype(auto) constexpr_switch_impl(F&& f, E value, Def&& def) { + if constexpr(I < End) { + constexpr auto v = enum_constant()>{}; + if (value == v) { + if constexpr (std::is_invocable_r_v) { + return static_cast(std::forward(f)(v)); + } else { + return def(); + } + } else { + return constexpr_switch_impl(std::forward(f), value, std::forward(def)); + } + } else { + return def(); + } +} + +template +constexpr decltype(auto) constexpr_switch(F&& f, E value, Def&& def) { + static_assert(is_enum_v, "magic_enum::detail::constexpr_switch requires enum type."); + + if constexpr (count_v == 0) { + return def(); + } else { + return constexpr_switch_impl<0, count_v, R, E, S>(std::forward(f), value, std::forward(def)); + } +} +#endif + +} // namespace magic_enum::detail + +template , typename F, typename R = detail::result_t> +constexpr decltype(auto) enum_switch(F&& f, E value) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_switch requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + std::forward(f), + value, + detail::default_result_type_lambda); +#else + return detail::constexpr_switch( + std::forward(f), + value, + detail::default_result_type_lambda); +#endif +} + +template > +constexpr decltype(auto) enum_switch(F&& f, E value) { + return enum_switch(std::forward(f), value); +} + +template , typename F, typename R = detail::result_t> +constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_switch requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + std::forward(f), + value, + [&result]() -> R { return std::forward(result); }); +#else + return detail::constexpr_switch( + std::forward(f), + value, + [&result]() -> R { return std::forward(result); }); +#endif +} + +template > +constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) { + return enum_switch(std::forward(f), value, std::forward(result)); +} + +} // namespace magic_enum + +template <> +struct std::common_type : magic_enum::detail::identity {}; + +template +struct std::common_type : magic_enum::detail::identity {}; + +template +struct std::common_type : magic_enum::detail::identity {}; + +#endif // NEARGYE_MAGIC_ENUM_SWITCH_HPP diff --git a/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_utility.hpp b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_utility.hpp new file mode 100644 index 0000000..5368b02 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/hulc/magic_enum/magic_enum/magic_enum_utility.hpp @@ -0,0 +1,138 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_UTILITY_HPP +#define NEARGYE_MAGIC_ENUM_UTILITY_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +template +constexpr auto for_each(F&& f, std::index_sequence) { + constexpr bool has_void_return = (std::is_void_v[I]>>> || ...); + constexpr bool all_same_return = (std::is_same_v[0]>>, std::invoke_result_t[I]>>> && ...); + + if constexpr (has_void_return) { + (f(enum_constant[I]>{}), ...); + } else if constexpr (all_same_return) { + return std::array{f(enum_constant[I]>{})...}; + } else { + return std::tuple{f(enum_constant[I]>{})...}; + } +} + +template +constexpr bool all_invocable(std::index_sequence) { + if constexpr (count_v == 0) { + return false; + } else { + return (std::is_invocable_v[I]>> && ...); + } +} + +} // namespace magic_enum::detail + +template , typename F, detail::enable_if_t = 0> +constexpr auto enum_for_each(F&& f) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_for_each requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + constexpr auto sep = std::make_index_sequence>{}; + + if constexpr (detail::all_invocable(sep)) { + return detail::for_each(std::forward(f), sep); + } else { + static_assert(detail::always_false_v, "magic_enum::enum_for_each requires invocable of all enum value."); + } +} + +template > +[[nodiscard]] constexpr auto enum_next_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = (static_cast(*i) + n); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return {}; +} + +template > +[[nodiscard]] constexpr auto enum_next_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = ((((static_cast(*i) + n) % count) + count) % count); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return MAGIC_ENUM_ASSERT(false), value; +} + +template > +[[nodiscard]] constexpr auto enum_prev_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = (static_cast(*i) - n); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return {}; +} + +template > +[[nodiscard]] constexpr auto enum_prev_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = ((((static_cast(*i) - n) % count) + count) % count); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return MAGIC_ENUM_ASSERT(false), value; +} + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_UTILITY_HPP diff --git a/module/dll_source_lzy/114/out/lib/log.txt b/module/dll_source_lzy/114/out/lib/log.txt new file mode 100644 index 0000000..b310560 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/log.txt @@ -0,0 +1,1443 @@ +findDevices success! +openDevice success! + addr = 0x42c0val = 0 +[func~] Server_Socket:[0.0.0.0:10003] 添加了连接 [fd:12:[192.168.1.110:49424]] + addr = 0x42e4val = 1762950288 + addr = 0x42e0val = 0 +startDevRecv success! + addr = 0x42c0val = 0 + addr = 0x42e4val = 1762950288 + addr = 0x42e0val = 0 +startDevRecv success! +chstrRX0 dataType:7 ZDCh0 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4280val = 0 + addr = 0x4284val = 0 + addr = 0x4280val = 0 + addr = 0x4284val = 0 + addr = 0x4210val = 3 +chstrRX0 dataType:6 ZDCh0 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4220val = 0 + addr = 0x4220val = 0 + addr = 0x4210val = 3 +chstrRX0 dataType:5 ZDCh0 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4280val = 0 + addr = 0x4284val = 0 + addr = 0x4240val = 0 + addr = 0x4240val = 0 + addr = 0x4210val = 3 +chstrRX1 dataType:7 ZDCh8 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4210val = 3 +chstrRX1 dataType:6 ZDCh0 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4220val = 0 + addr = 0x4220val = 0 + addr = 0x4210val = 3 +chstrRX1 dataType:5 ZDCh0 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4280val = 0 + addr = 0x4284val = 0 + addr = 0x4240val = 0 + addr = 0x4240val = 0 + addr = 0x4210val = 3 +chstrRX0 paramType:0 paramValue1.089e+09 + addr = 0x4210val = 0 + addr = 0x4234val = 0 + addr = 0x4208val = 1 + addr = 0x4264val = 0 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4268val = 0 + addr = 0x4248val = 0 + addr = 0x4244val = 0 + addr = 0x424cval = 0 + addr = 0x4250val = 1089000000 + addr = 0x4210val = 3 +chstrRX0 paramType:1 paramValue6e+06 + addr = 0x4210val = 0 + addr = 0x4234val = 0 + addr = 0x4208val = 1 + addr = 0x4264val = 0 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4268val = 0 + addr = 0x4248val = 6000000 + addr = 0x4244val = 0 + addr = 0x424cval = 0 + addr = 0x4250val = 1089000000 + addr = 0x4210val = 3 +chstrRX0 paramType:2 paramValue2.4e+07 + addr = 0x4210val = 0 + addr = 0x4234val = 0 + addr = 0x4208val = 1 + addr = 0x4264val = 0 + addr = 0x4270val = -20 + addr = 0x4270val = -50 + addr = 0x4270val = -52 + addr = 0x4270val = -22 + addr = 0x4270val = 24 + addr = 0x4270val = 62 + addr = 0x4270val = 67 + addr = 0x4270val = 30 + addr = 0x4270val = -33 + addr = 0x4270val = -88 + addr = 0x4270val = -97 + addr = 0x4270val = -45 + addr = 0x4270val = 49 + addr = 0x4270val = 131 + addr = 0x4270val = 145 + addr = 0x4270val = 66 + addr = 0x4270val = -73 + addr = 0x4270val = -194 + addr = 0x4270val = -213 + addr = 0x4270val = -97 + addr = 0x4270val = 106 + addr = 0x4270val = 278 + addr = 0x4270val = 304 + addr = 0x4270val = 137 + addr = 0x4270val = -149 + addr = 0x4270val = -390 + addr = 0x4270val = -423 + addr = 0x4270val = -190 + addr = 0x4270val = 205 + addr = 0x4270val = 534 + addr = 0x4270val = 576 + addr = 0x4270val = 257 + addr = 0x4270val = -277 + addr = 0x4270val = -719 + addr = 0x4270val = -773 + addr = 0x4270val = -344 + addr = 0x4270val = 370 + addr = 0x4270val = 958 + addr = 0x4270val = 1029 + addr = 0x4270val = 458 + addr = 0x4270val = -491 + addr = 0x4270val = -1275 + addr = 0x4270val = -1370 + addr = 0x4270val = -610 + addr = 0x4270val = 657 + addr = 0x4270val = 1711 + addr = 0x4270val = 1848 + addr = 0x4270val = 828 + addr = 0x4270val = -898 + addr = 0x4270val = -2359 + addr = 0x4270val = -2576 + addr = 0x4270val = -1170 + addr = 0x4270val = 1289 + addr = 0x4270val = 3452 + addr = 0x4270val = 3859 + addr = 0x4270val = 1805 + addr = 0x4270val = -2064 + addr = 0x4270val = -5796 + addr = 0x4270val = -6897 + addr = 0x4270val = -3511 + addr = 0x4270val = 4535 + addr = 0x4270val = 15380 + addr = 0x4270val = 25691 + addr = 0x4270val = 31960 + addr = 0x4270val = 31960 + addr = 0x4270val = 25691 + addr = 0x4270val = 15380 + addr = 0x4270val = 4535 + addr = 0x4270val = -3511 + addr = 0x4270val = -6897 + addr = 0x4270val = -5796 + addr = 0x4270val = -2064 + addr = 0x4270val = 1805 + addr = 0x4270val = 3859 + addr = 0x4270val = 3452 + addr = 0x4270val = 1289 + addr = 0x4270val = -1170 + addr = 0x4270val = -2576 + addr = 0x4270val = -2359 + addr = 0x4270val = -898 + addr = 0x4270val = 828 + addr = 0x4270val = 1848 + addr = 0x4270val = 1711 + addr = 0x4270val = 657 + addr = 0x4270val = -610 + addr = 0x4270val = -1370 + addr = 0x4270val = -1275 + addr = 0x4270val = -491 + addr = 0x4270val = 458 + addr = 0x4270val = 1029 + addr = 0x4270val = 958 + addr = 0x4270val = 370 + addr = 0x4270val = -344 + addr = 0x4270val = -773 + addr = 0x4270val = -719 + addr = 0x4270val = -277 + addr = 0x4270val = 257 + addr = 0x4270val = 576 + addr = 0x4270val = 534 + addr = 0x4270val = 205 + addr = 0x4270val = -190 + addr = 0x4270val = -423 + addr = 0x4270val = -390 + addr = 0x4270val = -149 + addr = 0x4270val = 137 + addr = 0x4270val = 304 + addr = 0x4270val = 278 + addr = 0x4270val = 106 + addr = 0x4270val = -97 + addr = 0x4270val = -213 + addr = 0x4270val = -194 + addr = 0x4270val = -73 + addr = 0x4270val = 66 + addr = 0x4270val = 145 + addr = 0x4270val = 131 + addr = 0x4270val = 49 + addr = 0x4270val = -45 + addr = 0x4270val = -97 + addr = 0x4270val = -88 + addr = 0x4270val = -33 + addr = 0x4270val = 30 + addr = 0x4270val = 67 + addr = 0x4270val = 62 + addr = 0x4270val = 24 + addr = 0x4270val = -22 + addr = 0x4270val = -52 + addr = 0x4270val = -50 + addr = 0x4270val = -20 + addr = 0x4268val = 0 + addr = 0x4248val = 6000000 + addr = 0x4244val = 24000000 + addr = 0x424cval = 0 + addr = 0x4250val = 1089000000 + addr = 0x4210val = 3 +chstrRX0 paramType:3 paramValue30 + addr = 0x4210val = 0 +chstrRX1 paramType:0 paramValue1.5e+07 + addr = 0x4210val = 0 + addr = 0x423cval = 0 + addr = 0x427cval = 0 + addr = 0x4208val = 1 + addr = 0x4264val = 1 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x426cval = 0 + addr = 0x4258val = 0 + addr = 0x4254val = 0 + addr = 0x425cval = 0 + addr = 0x4260val = 15000000 + addr = 0x4210val = 3 +chstrRX1 paramType:1 paramValue3e+07 + addr = 0x4210val = 0 + addr = 0x423cval = 0 + addr = 0x427cval = 0 + addr = 0x4208val = 1 + addr = 0x4264val = 1 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x4270val = -2147483648 + addr = 0x426cval = 0 + addr = 0x4258val = 30000000 + addr = 0x4254val = 0 + addr = 0x425cval = 0 + addr = 0x4260val = 15000000 + addr = 0x4210val = 3 +chstrRX1 paramType:2 paramValue6.144e+07 + addr = 0x4210val = 0 + addr = 0x423cval = 0 + addr = 0x427cval = 0 + addr = 0x4208val = 1 + addr = 0x4264val = 1 + addr = 0x4270val = -1 + addr = 0x4270val = 54 + addr = 0x4270val = 5 + addr = 0x4270val = -58 + addr = 0x4270val = -10 + addr = 0x4270val = 66 + addr = 0x4270val = 17 + addr = 0x4270val = -76 + addr = 0x4270val = -27 + addr = 0x4270val = 90 + addr = 0x4270val = 40 + addr = 0x4270val = -106 + addr = 0x4270val = -57 + addr = 0x4270val = 125 + addr = 0x4270val = 80 + addr = 0x4270val = -145 + addr = 0x4270val = -109 + addr = 0x4270val = 167 + addr = 0x4270val = 145 + addr = 0x4270val = -190 + addr = 0x4270val = -189 + addr = 0x4270val = 212 + addr = 0x4270val = 242 + addr = 0x4270val = -232 + addr = 0x4270val = -305 + addr = 0x4270val = 249 + addr = 0x4270val = 378 + addr = 0x4270val = -262 + addr = 0x4270val = -464 + addr = 0x4270val = 269 + addr = 0x4270val = 561 + addr = 0x4270val = -268 + addr = 0x4270val = -672 + addr = 0x4270val = 257 + addr = 0x4270val = 798 + addr = 0x4270val = -234 + addr = 0x4270val = -940 + addr = 0x4270val = 196 + addr = 0x4270val = 1099 + addr = 0x4270val = -139 + addr = 0x4270val = -1278 + addr = 0x4270val = 59 + addr = 0x4270val = 1481 + addr = 0x4270val = 49 + addr = 0x4270val = -1711 + addr = 0x4270val = -193 + addr = 0x4270val = 1977 + addr = 0x4270val = 383 + addr = 0x4270val = -2290 + addr = 0x4270val = -635 + addr = 0x4270val = 2669 + addr = 0x4270val = 975 + addr = 0x4270val = -3146 + addr = 0x4270val = -1449 + addr = 0x4270val = 3781 + addr = 0x4270val = 2143 + addr = 0x4270val = -4703 + addr = 0x4270val = -3254 + addr = 0x4270val = 6223 + addr = 0x4270val = 5322 + addr = 0x4270val = -9373 + addr = 0x4270val = -10624 + addr = 0x4270val = 20689 + addr = 0x4270val = 57875 + addr = 0x4270val = 57875 + addr = 0x4270val = 20689 + addr = 0x4270val = -10624 + addr = 0x4270val = -9373 + addr = 0x4270val = 5322 + addr = 0x4270val = 6223 + addr = 0x4270val = -3254 + addr = 0x4270val = -4703 + addr = 0x4270val = 2143 + addr = 0x4270val = 3781 + addr = 0x4270val = -1449 + addr = 0x4270val = -3146 + addr = 0x4270val = 975 + addr = 0x4270val = 2669 + addr = 0x4270val = -635 + addr = 0x4270val = -2290 + addr = 0x4270val = 383 + addr = 0x4270val = 1977 + addr = 0x4270val = -193 + addr = 0x4270val = -1711 + addr = 0x4270val = 49 + addr = 0x4270val = 1481 + addr = 0x4270val = 59 + addr = 0x4270val = -1278 + addr = 0x4270val = -139 + addr = 0x4270val = 1099 + addr = 0x4270val = 196 + addr = 0x4270val = -940 + addr = 0x4270val = -234 + addr = 0x4270val = 798 + addr = 0x4270val = 257 + addr = 0x4270val = -672 + addr = 0x4270val = -268 + addr = 0x4270val = 561 + addr = 0x4270val = 269 + addr = 0x4270val = -464 + addr = 0x4270val = -262 + addr = 0x4270val = 378 + addr = 0x4270val = 249 + addr = 0x4270val = -305 + addr = 0x4270val = -232 + addr = 0x4270val = 242 + addr = 0x4270val = 212 + addr = 0x4270val = -189 + addr = 0x4270val = -190 + addr = 0x4270val = 145 + addr = 0x4270val = 167 + addr = 0x4270val = -109 + addr = 0x4270val = -145 + addr = 0x4270val = 80 + addr = 0x4270val = 125 + addr = 0x4270val = -57 + addr = 0x4270val = -106 + addr = 0x4270val = 40 + addr = 0x4270val = 90 + addr = 0x4270val = -27 + addr = 0x4270val = -76 + addr = 0x4270val = 17 + addr = 0x4270val = 66 + addr = 0x4270val = -10 + addr = 0x4270val = -58 + addr = 0x4270val = 5 + addr = 0x4270val = 54 + addr = 0x4270val = -1 + addr = 0x426cval = 0 + addr = 0x4258val = 30000000 + addr = 0x4254val = 61440000 + addr = 0x425cval = 0 + addr = 0x4260val = 15000000 + addr = 0x4210val = 3 +chstrRX1 paramType:3 paramValue30 + addr = 0x4210val = 0 +chstr:RX0 centerfreq:1090000000 bw:3000000 sample:12000000 + addr = 0x4280val = 0 + addr = 0x4288val = 1 + addr = 0x428cval = 3000000 + addr = 0x4290val = 178956970 + addr = 0x4294val = -20 + addr = 0x4294val = -50 + addr = 0x4294val = -52 + addr = 0x4294val = -22 + addr = 0x4294val = 24 + addr = 0x4294val = 62 + addr = 0x4294val = 67 + addr = 0x4294val = 30 + addr = 0x4294val = -33 + addr = 0x4294val = -88 + addr = 0x4294val = -97 + addr = 0x4294val = -45 + addr = 0x4294val = 49 + addr = 0x4294val = 131 + addr = 0x4294val = 145 + addr = 0x4294val = 66 + addr = 0x4294val = -73 + addr = 0x4294val = -194 + addr = 0x4294val = -213 + addr = 0x4294val = -97 + addr = 0x4294val = 106 + addr = 0x4294val = 278 + addr = 0x4294val = 304 + addr = 0x4294val = 137 + addr = 0x4294val = -149 + addr = 0x4294val = -390 + addr = 0x4294val = -423 + addr = 0x4294val = -190 + addr = 0x4294val = 205 + addr = 0x4294val = 534 + addr = 0x4294val = 576 + addr = 0x4294val = 257 + addr = 0x4294val = -277 + addr = 0x4294val = -719 + addr = 0x4294val = -773 + addr = 0x4294val = -344 + addr = 0x4294val = 370 + addr = 0x4294val = 958 + addr = 0x4294val = 1029 + addr = 0x4294val = 458 + addr = 0x4294val = -491 + addr = 0x4294val = -1275 + addr = 0x4294val = -1370 + addr = 0x4294val = -610 + addr = 0x4294val = 657 + addr = 0x4294val = 1711 + addr = 0x4294val = 1848 + addr = 0x4294val = 828 + addr = 0x4294val = -898 + addr = 0x4294val = -2359 + addr = 0x4294val = -2576 + addr = 0x4294val = -1170 + addr = 0x4294val = 1289 + addr = 0x4294val = 3452 + addr = 0x4294val = 3859 + addr = 0x4294val = 1805 + addr = 0x4294val = -2064 + addr = 0x4294val = -5796 + addr = 0x4294val = -6897 + addr = 0x4294val = -3511 + addr = 0x4294val = 4535 + addr = 0x4294val = 15380 + addr = 0x4294val = 25691 + addr = 0x4294val = 31960 + addr = 0x4294val = 31960 + addr = 0x4294val = 25691 + addr = 0x4294val = 15380 + addr = 0x4294val = 4535 + addr = 0x4294val = -3511 + addr = 0x4294val = -6897 + addr = 0x4294val = -5796 + addr = 0x4294val = -2064 + addr = 0x4294val = 1805 + addr = 0x4294val = 3859 + addr = 0x4294val = 3452 + addr = 0x4294val = 1289 + addr = 0x4294val = -1170 + addr = 0x4294val = -2576 + addr = 0x4294val = -2359 + addr = 0x4294val = -898 + addr = 0x4294val = 828 + addr = 0x4294val = 1848 + addr = 0x4294val = 1711 + addr = 0x4294val = 657 + addr = 0x4294val = -610 + addr = 0x4294val = -1370 + addr = 0x4294val = -1275 + addr = 0x4294val = -491 + addr = 0x4294val = 458 + addr = 0x4294val = 1029 + addr = 0x4294val = 958 + addr = 0x4294val = 370 + addr = 0x4294val = -344 + addr = 0x4294val = -773 + addr = 0x4294val = -719 + addr = 0x4294val = -277 + addr = 0x4294val = 257 + addr = 0x4294val = 576 + addr = 0x4294val = 534 + addr = 0x4294val = 205 + addr = 0x4294val = -190 + addr = 0x4294val = -423 + addr = 0x4294val = -390 + addr = 0x4294val = -149 + addr = 0x4294val = 137 + addr = 0x4294val = 304 + addr = 0x4294val = 278 + addr = 0x4294val = 106 + addr = 0x4294val = -97 + addr = 0x4294val = -213 + addr = 0x4294val = -194 + addr = 0x4294val = -73 + addr = 0x4294val = 66 + addr = 0x4294val = 145 + addr = 0x4294val = 131 + addr = 0x4294val = 49 + addr = 0x4294val = -45 + addr = 0x4294val = -97 + addr = 0x4294val = -88 + addr = 0x4294val = -33 + addr = 0x4294val = 30 + addr = 0x4294val = 67 + addr = 0x4294val = 62 + addr = 0x4294val = 24 + addr = 0x4294val = -22 + addr = 0x4294val = -52 + addr = 0x4294val = -50 + addr = 0x4294val = -20 + addr = 0x4298val = 12000000 +external + addr = 0x4200val = 10 + REF = external +chstrRX0 dataType:7 ZDCh0 bSwitch1 + addr = 0x4210val = 0 + addr = 0x4280val = 0 + addr = 0x4284val = 0 + addr = 0x4280val = 0 + addr = 0x4284val = 1 + addr = 0x4210val = 3 + set 9002 genneric success! +adsb_set_less_30Mhz_ddc_param 15000000 30000 20000 +chstrRX1 dataType:7 ZDCh8 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4210val = 3 +chstr:RX1 centerfreq:15000000 bw:20000 sample:30000 + addr = 0x4280val = 8 + addr = 0x4288val = 11 + addr = 0x428cval = 20000 + addr = 0x4290val = 0 + addr = 0x4294val = 46 + addr = 0x4294val = -47 + addr = 0x4294val = 0 + addr = 0x4294val = 51 + addr = 0x4294val = -54 + addr = 0x4294val = 0 + addr = 0x4294val = 63 + addr = 0x4294val = -69 + addr = 0x4294val = 0 + addr = 0x4294val = 83 + addr = 0x4294val = -91 + addr = 0x4294val = 0 + addr = 0x4294val = 111 + addr = 0x4294val = -123 + addr = 0x4294val = 0 + addr = 0x4294val = 150 + addr = 0x4294val = -165 + addr = 0x4294val = 0 + addr = 0x4294val = 199 + addr = 0x4294val = -218 + addr = 0x4294val = 0 + addr = 0x4294val = 261 + addr = 0x4294val = -284 + addr = 0x4294val = 0 + addr = 0x4294val = 336 + addr = 0x4294val = -365 + addr = 0x4294val = 0 + addr = 0x4294val = 428 + addr = 0x4294val = -463 + addr = 0x4294val = 0 + addr = 0x4294val = 539 + addr = 0x4294val = -581 + addr = 0x4294val = 0 + addr = 0x4294val = 673 + addr = 0x4294val = -724 + addr = 0x4294val = 0 + addr = 0x4294val = 835 + addr = 0x4294val = -897 + addr = 0x4294val = 0 + addr = 0x4294val = 1034 + addr = 0x4294val = -1111 + addr = 0x4294val = 0 + addr = 0x4294val = 1283 + addr = 0x4294val = -1380 + addr = 0x4294val = 0 + addr = 0x4294val = 1602 + addr = 0x4294val = -1730 + addr = 0x4294val = 0 + addr = 0x4294val = 2030 + addr = 0x4294val = -2209 + addr = 0x4294val = 0 + addr = 0x4294val = 2644 + addr = 0x4294val = -2914 + addr = 0x4294val = 0 + addr = 0x4294val = 3613 + addr = 0x4294val = -4079 + addr = 0x4294val = 0 + addr = 0x4294val = 5426 + addr = 0x4294val = -6457 + addr = 0x4294val = 0 + addr = 0x4294val = 10250 + addr = 0x4294val = -14399 + addr = 0x4294val = 0 + addr = 0x4294val = 72237 + addr = 0x4294val = 72237 + addr = 0x4294val = 0 + addr = 0x4294val = -14399 + addr = 0x4294val = 10250 + addr = 0x4294val = 0 + addr = 0x4294val = -6457 + addr = 0x4294val = 5426 + addr = 0x4294val = 0 + addr = 0x4294val = -4079 + addr = 0x4294val = 3613 + addr = 0x4294val = 0 + addr = 0x4294val = -2914 + addr = 0x4294val = 2644 + addr = 0x4294val = 0 + addr = 0x4294val = -2209 + addr = 0x4294val = 2030 + addr = 0x4294val = 0 + addr = 0x4294val = -1730 + addr = 0x4294val = 1602 + addr = 0x4294val = 0 + addr = 0x4294val = -1380 + addr = 0x4294val = 1283 + addr = 0x4294val = 0 + addr = 0x4294val = -1111 + addr = 0x4294val = 1034 + addr = 0x4294val = 0 + addr = 0x4294val = -897 + lose frame current_index0 lastframindex_ch0_zdddc0 + addr = 0x4294val = 835 + addr = 0x4294val = 0 + addr = 0x4294val = -724 + addr = 0x4294val = 673 + addr = 0x4294val = 0 + addr = 0x4294val = -581 + addr = 0x4294val = 539 + addr = 0x4294val = 0 + addr = 0x4294val = -463 + addr = 0x4294val = 428 + addr = 0x4294val = 0 + addr = 0x4294val = -365 + addr = 0x4294val = 336 + addr = 0x4294val = 0 + addr = 0x4294val = -284 + addr = 0x4294val = 261 + addr = 0x4294val = 0 + addr = 0x4294val = -218 + addr = 0x4294val = 199 + addr = 0x4294val = 0 + addr = 0x4294val = -165 + addr = 0x4294val = 150 + addr = 0x4294val = 0 + addr = 0x4294val = -123 + addr = 0x4294val = 111 + addr = 0x4294val = 0 + addr = 0x4294val = -91 + addr = 0x4294val = 83 + addr = 0x4294val = 0 + addr = 0x4294val = -69 + addr = 0x4294val = 63 + addr = 0x4294val = 0 + addr = 0x4294val = -54 + addr = 0x4294val = 51 + addr = 0x4294val = 0 + addr = 0x4294val = -47 + addr = 0x4294val = 46 + addr = 0x4298val = 30000 +chstrRX1 dataType:7 ZDCh8 bSwitch1 + addr = 0x4210val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 1 + addr = 0x4210val = 3 +adsb_set_less_30Mhz_fft_param 3 1024 100 +chstrRX1 dataType:6 ZDCh8 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4220val = 0 + addr = 0x4220val = 0 + addr = 0x4210val = 3 + addr = 0x4210val = 0 + addr = 0x4228val = 3 + addr = 0x4224val = 1024 + addr = 0x422cval = 100 + addr = 0x4210val = 3 +chstrRX1 dataType:6 ZDCh8 bSwitch1 + addr = 0x4210val = 0 + addr = 0x4220val = 0 + addr = 0x4220val = 2 + addr = 0x4210val = 3 +adsb_set_iq_cbk 0x55c27c214cc0 1 + lose frame current_index6288 lastframindex_ch0_zdddc2047 + lose frame current_index0 lastframindex_ch0_zdddc7340 + + + lose frame current_index0 lastframindex_ch1_zdddc0 + lose frame current_index0 lastframindex_ch1_fft0 + + + +adsb_set_less_30Mhz_ddc_param 15000000 30000 15000 +chstrRX1 dataType:7 ZDCh8 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4210val = 3 +chstr:RX1 centerfreq:15000000 bw:15000 sample:30000 + addr = 0x4280val = 8 + addr = 0x4288val = 11 + addr = 0x428cval = 15000 + addr = 0x4290val = 0 + addr = 0x4294val = -37 + addr = 0x4294val = -38 + addr = 0x4294val = 39 + addr = 0x4294val = 41 + addr = 0x4294val = -44 + addr = 0x4294val = -47 + addr = 0x4294val = 51 + addr = 0x4294val = 56 + addr = 0x4294val = -61 + addr = 0x4294val = -68 + addr = 0x4294val = 75 + addr = 0x4294val = 82 + addr = 0x4294val = -91 + addr = 0x4294val = -100 + addr = 0x4294val = 111 + addr = 0x4294val = 122 + addr = 0x4294val = -135 + addr = 0x4294val = -148 + addr = 0x4294val = 163 + addr = 0x4294val = 178 + addr = 0x4294val = -195 + addr = 0x4294val = -213 + addr = 0x4294val = 232 + addr = 0x4294val = 253 + addr = 0x4294val = -275 + addr = 0x4294val = -298 + addr = 0x4294val = 323 + addr = 0x4294val = 350 + addr = 0x4294val = -378 + addr = 0x4294val = -409 + addr = 0x4294val = 441 + addr = 0x4294val = 475 + addr = 0x4294val = -511 + addr = 0x4294val = -550 + addr = 0x4294val = 591 + addr = 0x4294val = 636 + addr = 0x4294val = -683 + addr = 0x4294val = -733 + addr = 0x4294val = 787 + addr = 0x4294val = 845 + addr = 0x4294val = -908 + addr = 0x4294val = -975 + addr = 0x4294val = 1048 + addr = 0x4294val = 1127 + addr = 0x4294val = -1214 + addr = 0x4294val = -1309 + addr = 0x4294val = 1414 + addr = 0x4294val = 1530 + addr = 0x4294val = -1659 + addr = 0x4294val = -1805 + addr = 0x4294val = 1970 + addr = 0x4294val = 2160 + addr = 0x4294val = -2381 + addr = 0x4294val = -2641 + addr = 0x4294val = 2952 + addr = 0x4294val = 3334 + addr = 0x4294val = -3813 + addr = 0x4294val = -4434 + addr = 0x4294val = 5276 + addr = 0x4294val = 6485 + addr = 0x4294val = -8376 + addr = 0x4294val = -11766 + addr = 0x4294val = 19654 + addr = 0x4294val = 59027 + addr = 0x4294val = 59027 + addr = 0x4294val = 19654 + addr = 0x4294val = -11766 + addr = 0x4294val = -8376 + addr = 0x4294val = 6485 + addr = 0x4294val = 5276 + addr = 0x4294val = -4434 + addr = 0x4294val = -3813 + addr = 0x4294val = 3334 + addr = 0x4294val = 2952 + addr = 0x4294val = -2641 + addr = 0x4294val = -2381 + addr = 0x4294val = 2160 + addr = 0x4294val = 1970 + addr = 0x4294val = -1805 + addr = 0x4294val = -1659 + addr = 0x4294val = 1530 + addr = 0x4294val = 1414 + addr = 0x4294val = -1309 + addr = 0x4294val = -1214 + lose frame current_index0 lastframindex_ch0_zdddc16084 + addr = 0x4294val = 1127 + addr = 0x4294val = 1048 + addr = 0x4294val = -975 + addr = 0x4294val = -908 + addr = 0x4294val = 845 + addr = 0x4294val = 787 + addr = 0x4294val = -733 + addr = 0x4294val = -683 + addr = 0x4294val = 636 + addr = 0x4294val = 591 + addr = 0x4294val = -550 + addr = 0x4294val = -511 + addr = 0x4294val = 475 + addr = 0x4294val = 441 + addr = 0x4294val = -409 + addr = 0x4294val = -378 + addr = 0x4294val = 350 + addr = 0x4294val = 323 + addr = 0x4294val = -298 + addr = 0x4294val = -275 + addr = 0x4294val = 253 + addr = 0x4294val = 232 + addr = 0x4294val = -213 + addr = 0x4294val = -195 + addr = 0x4294val = 178 + addr = 0x4294val = 163 + addr = 0x4294val = -148 + addr = 0x4294val = -135 + addr = 0x4294val = 122 + addr = 0x4294val = 111 + addr = 0x4294val = -100 + addr = 0x4294val = -91 + addr = 0x4294val = 82 + addr = 0x4294val = 75 + addr = 0x4294val = -68 addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4294val = -61 + addr = 0x4294val = 56 + addr = 0x4294val = 51 + addr = 0x4294val = -47 + addr = 0x4294val = -44 + addr = 0x4294val = 41 + addr = 0x4294val = 39 + addr = 0x4294val = -38 + addr = 0x4294val = -37 + addr = 0x4298val = 30000 +chstrRX1 dataType:7 ZDCh8 bSwitch1 + addr = 0x4210val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 0 + addr = 0x4280val = 8 + addr = 0x4284val = 1 + addr = 0x4210val = 3 + lose frame current_index0 lastframindex_ch0_zdddc7128 + lose frame current_index0 lastframindex_ch1_zdddc4469 +^A + lose frame current_index12621 lastframindex_ch0_zdddc12596 + lose frame current_index14850 lastframindex_ch0_zdddc14668 + lose frame current_index17153 lastframindex_ch0_zdddc16897 + lose frame current_index19326 lastframindex_ch0_zdddc19200 + lose frame current_index21441 lastframindex_ch0_zdddc21373 + lose frame current_index23565 lastframindex_ch0_zdddc23488 + lose frame current_index25652 lastframindex_ch0_zdddc25612 + lose frame current_index27887 lastframindex_ch0_zdddc27698 + lose frame current_index30061 lastframindex_ch0_zdddc29933 + lose frame current_index38279 lastframindex_ch0_zdddc38252 + lose frame current_index40404 lastframindex_ch0_zdddc40326 + lose frame current_index44538 lastframindex_ch0_zdddc44499 + lose frame current_index46791 lastframindex_ch0_zdddc46584 + lose frame current_index48851 lastframindex_ch0_zdddc48838 + lose frame current_index51321 lastframindex_ch0_zdddc50898 + lose frame current_index53483 lastframindex_ch0_zdddc53368 + lose frame current_index63789 lastframindex_ch0_zdddc63722 + lose frame current_index366 lastframindex_ch0_zdddc300 + lose frame current_index2509 lastframindex_ch0_zdddc2413 + lose frame current_index5149 lastframindex_ch0_zdddc4555 + lose frame current_index7454 lastframindex_ch0_zdddc7195 + lose frame current_index9548 lastframindex_ch0_zdddc9501 + lose frame current_index11749 lastframindex_ch0_zdddc11595 + lose frame current_index13821 lastframindex_ch0_zdddc13796 + lose frame current_index15940 lastframindex_ch0_zdddc15868 + lose frame current_index18058 lastframindex_ch0_zdddc17987 + lose frame current_index20596 lastframindex_ch0_zdddc20104 + lose frame current_index22755 lastframindex_ch0_zdddc22643 +^C[SIGINT (Ctrl+C)] + lose frame current_index24879 lastframindex_ch0_zdddc24802 + lose frame current_index26999 lastframindex_ch0_zdddc26926 + lose frame current_index29568 lastframindex_ch0_zdddc29046 + lose frame current_index31761 lastframindex_ch0_zdddc31614 + lose frame current_index33927 lastframindex_ch0_zdddc33808 + lose frame current_index36041 lastframindex_ch0_zdddc35974 + lose frame current_index38389 lastframindex_ch0_zdddc38088 + lose frame current_index40441 lastframindex_ch0_zdddc40436 + lose frame current_index42725 lastframindex_ch0_zdddc42487 + lose frame current_index44902 lastframindex_ch0_zdddc44771 + lose frame current_index49028 lastframindex_ch0_zdddc48997 + lose frame current_index51431 lastframindex_ch0_zdddc51075 + lose frame current_index53722 lastframindex_ch0_zdddc53477 +耗时: 14 毫秒 Data_Source_Handle_Thread:[dll]退出! +耗时: 8 毫秒 Data_Source_Handle_Thread:[radarcape]退出! +耗时: 0 毫秒 data_feed_thread退出! +耗时: 0 毫秒 web服务器线程退出! +chstrRX0 dataType:7 ZDCh0 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4280val = 0 + addr = 0x4284val = 0 + addr = 0x4280val = 0 + addr = 0x4284val = 0 + addr = 0x4210val = 3 +chstrRX0 dataType:5 ZDCh0 bSwitch0 + addr = 0x4210val = 0 + addr = 0x4280val = 0 + addr = 0x4284val = 0 + addr = 0x4240val = 0 + addr = 0x4240val = 0 + addr = 0x4210val = 3 +^C[SIGINT (Ctrl+C)] +malloc_consolidate(): invalid chunk size +Stack trace written to stack_trace_2025-11-12_20-27-16.txt +SIGSEGV (Segmentation Fault) 意料之外的信号捕捉到! +Stack trace: +#0 ./rls(+0x1a8534) [0x55c27bf45534] +#1 signal_handler(int) ??:? ./rls(+0x53061) [0x55c27bdf0061] +#2 yC::serial::set_baud_rate(termios&, int, unsigned int) [clone .cold] Serial_linux.cpp:? /lib/x86_64-linux-gnu/libc.so.6(+0x43090) [0x7f5c8fa47090] +#3 get_current_time[abi:cxx11]() ??:? /lib/x86_64-linux-gnu/libc.so.6(+0x168915) [0x7f5c8fb6c915] +#4 ?? ??:0 /home/heco/ys_ecap_server/x86_linux_Release_lib9002so/lib9002.so(_Z15ThreadRecv_Func15ThreadRecvParam+0xfc5) [0x7f5c847c8895] +#5 ADS_B_T::BDS09_Airspeed::toJson() ??:? /lib/x86_64-linux-gnu/libstdc++.so.6(+0xd6df4) [0x7f5c8fe5bdf4] +#6 ?? ??:0 /lib/x86_64-linux-gnu/libpthread.so.0(+0x8609) [0x7f5c8fbfe609] +#7 ?? ??:0 /lib/x86_64-linux-gnu/libc.so.6(clone+0x43) [0x7f5c8fb23353] +Segmentation fault (core dumped) +heco@heco:~/ys_ecap_server$ + diff --git a/module/dll_source_lzy/114/out/lib/makelib_common.h b/module/dll_source_lzy/114/out/lib/makelib_common.h new file mode 100644 index 0000000..7205625 --- /dev/null +++ b/module/dll_source_lzy/114/out/lib/makelib_common.h @@ -0,0 +1,14 @@ +#pragma once +#define export 1 +#if defined(_MSC_VER) || defined(WIN64) || defined(WIN32) || defined(__NT__) +#define ex_export __declspec(dllexport) +#define ex_emport __declspec(dllimport) +#else // linux or mac +#define ex_export __attribute__((visibility("default"))) +#define ex_emport __attribute__((visibility("default"))) +#endif +#if defined(export) +#define ex_declare ex_export +#else +#define ex_declare ex_emport +#endif \ No newline at end of file diff --git a/module/dll_source_lzy/114/out/lib9002.so b/module/dll_source_lzy/114/out/lib9002.so new file mode 100644 index 0000000000000000000000000000000000000000..408323beea9219fd524fa3a5a07dbb907660200d GIT binary patch literal 417448 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 z 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

|J>(b`0c`{E&` zezHzI4k4>=bso7v$8R!zHeS}_5JPFym5#p5z{d=~LWE1R?+*-xg>iPuy@q@_USP;T zF26Ezu1pZ0+a_&|7r)r;pTvu;4!J$v*e+ccBp`5=f=?wQ><1n=4!Fkv49@4^h{c-* zx(oK%Q9Rr<&q(7)Blj|!JV%N*Z1N&$JWmC3fkW% zj8B;^|0Yh5hVh_f{J3V7fzJGb(So7&tT@@4Zii)Emu|e2fJ{6DZz$dVLz4U|!}xEq z{3ye|CPi*dH#YT_ucjIB9G^|Izn4lp2qWXw?-jBvjd(6hBmS$>i1Ye1WbDRtxu%c( znRNLj(#W8BL5Er(LI3Mxf3uH#yN~#sDh(QIuLV`zi%i3r23cjQcrtQCkC3&Bvf
0_Xo0=XthZs;SnCei)lDfUf$@OSEnr!#|) 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 literal 0 HcmV?d00001 diff --git a/module/dll_source_lzy/114/丢包测试记录.txt b/module/dll_source_lzy/114/丢包测试记录.txt new file mode 100644 index 0000000..65800af --- /dev/null +++ b/module/dll_source_lzy/114/丢包测试记录.txt @@ -0,0 +1,70 @@ +测试流程: +一 关闭自启动 + 1 远程到设备(192.168.1.102) + ssh heco@192.168.1.102 #输入密码 123465 + 2 关闭设备自启动 (将 目录 /home/heco/ys_ecap_server/protect.bash 内容中的最后一行注释掉) + vi /home/heco/ys_ecap_server/protect.bash + 命令模式下(j 往下一直 定位到 ${DIR}/pt > ${DIR}/pt.log) + 定位好之后 (I 由命令模式切换到 插入模式) ,此时输入 '#' 号,注释调该行 + 按esc键 (由插入模式转换为命令模式), 输入 :wq 此时强制保存退出 + 3 重启设备9002rm, 和本测试机器 + ssh root@172.22.0.2 /sbin/reboot #重启设备9002rm + sudo reboot #重启本机器 + 4 待重启好之后,查看线程中是否还存在 ecap_server + ssh heco@192.168.1.102 #输入密码 123465 , 重新登陆 + ps aux |grep ecap_server + 若发现 只有一个--color == xxxxecap_server , 说明只剩下查询时候有关ecap_server在运行,没有自启动线程在运行了 +二 测试丢包情况 + + +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[3] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4228] val:[3] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4224] val:[1024] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x422c] val:[10] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[3] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[2] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[3] +[testsever]:FILE:[test_s_main.cpp]FUNC:[main]==> changed_ch2_times:[14] +[testsever]:FILE:[test_s_main.cpp]FUNC:[main]==> loop 2:end +[testsever]:FILE:[test_s_main.cpp]FUNC:[main]==> loop flag 2:begin +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[3] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4228] val:[3] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4224] val:[1024] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x422c] val:[10] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[3] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[2] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[3] +[testsever]:FILE:[test_s_main.cpp]FUNC:[main]==> changed_ch2_times:[15] +[testsever]:FILE:[test_s_main.cpp]FUNC:[main]==> loop 2:end +[testsever]:FILE:[device_uhd_c.cpp]FUNC:[_rx_data_cbk_t]==> loseframe!!!=> channel:[1] current_index:[153] last_index:[23169] +[testsever]:FILE:[device_uhd_c.cpp]FUNC:[_rx_data_cbk_t]==> loseframe!!!=> channel:[1] current_index:[6] last_index:[229] +[testsever]:FILE:[test_s_main.cpp]FUNC:[main]==> loop flag 2:begin +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[3] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4228] val:[3] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4224] val:[1024] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x422c] val:[10] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[3] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[0] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4220] val:[2] +[testsever]:FILE:[device_uhd_cpp.cpp]FUNC:[set_esun_rx_addr_val]==> addr:[0x4210] val:[3] +[testsever]:FILE:[test_s_main.cpp]FUNC:[main]==> changed_ch2_times:[16] +[testsever]:FILE:[test_s_main.cpp]FUNC:[main]==> loop 2:end +^C +第15次循环的时候,出现 丢包现象 \ No newline at end of file diff --git a/module/dll_source_lzy/ecap/lib/.device_uhd_c.cpp.swp b/module/dll_source_lzy/ecap/lib/.device_uhd_c.cpp.swp new file mode 100644 index 0000000000000000000000000000000000000000..c2b24d4d1fe3f5425842b7f396e8fe6ea4c40433 GIT binary patch literal 20480 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 Z6e}ywJRZ_noKhT@XF+Nn4*?!c{|1!c|6Kq8 literal 0 HcmV?d00001 diff --git a/module/dll_source_lzy/ecap/lib/DDCSampTable.h b/module/dll_source_lzy/ecap/lib/DDCSampTable.h new file mode 100644 index 0000000..c9a8fbf --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/DDCSampTable.h @@ -0,0 +1,28 @@ +#ifndef DDCSAMPTABLE_H +#define DDCSAMPTABLE_H +#include + +// adsb +const double KDDCSampTab0[11][2] = {{24000000, 1}, {12000000, 2}, {6000000, 4}, {3000000, 8}, {1500000, 16}, {750000, 32}, {375000, 64}, {187500, 128}, {93750, 256}, {46875, 512}, {23437, 1024}}; +const string KDDCSampTabStr0[11] = {"24000000 ", "12000000 ", "6000000 ", "3000000 ", "1500000 ", "750000 ", "375000 ", "187500 ", "93750 ", "46875 ", "23437 "}; +const int KDDCSampIndxMin0 = 0; +const int KDDCSampIndxMax0 = 10; +const double define_KDDCSampleMax0 = 24000000; +const double X0 = 0.8; +const int ZDDCSampIndxMin0 = 0; +const int ZDDCSampIndxMax0 = 12; +const double ZDDCSampTab0[13] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; +const double TXSFTab0[11] = {24000000, 12000000, 6000000, 3000000, 1500000, 750000, 375000, 187500, 93750, 46875, 23437}; + +// less 30 mhz +const double KDDCSampTab1[11][2] = {{61440000, 1}, {30720000, 2}, {15360000, 4}, {7680000, 8}, {3840000, 16}, {1920000, 32}, {960000, 64}, {480000, 128}, {240000, 256}, {120000, 512}, {60000, 1024}}; +const string KDDCSampTabStr1[11] = {"61440000", "30720000", "15360000", "7680000", "3840000", "1920000", "960000", "480000", "240000", "120000", "60000"}; +const int KDDCSampIndxMin1 = 0; +const int KDDCSampIndxMax1 = 10; +const double define_KDDCSampleMax1 = 61440000; +const double X1 = 0.8; +const int ZDDCSampIndxMin1 = 0; +const int ZDDCSampIndxMax1 = 12; +const double ZDDCSampTab1[13] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; +const double TXSFTab1[11] = {61440000, 30720000, 15360000, 7680000, 3840000, 1920000, 960000, 480000, 240000, 120000, 60000}; +#endif // DDCSAMPTABLE_H diff --git a/module/dll_source_lzy/ecap/lib/FIRGenLowpass_no_ipp.h b/module/dll_source_lzy/ecap/lib/FIRGenLowpass_no_ipp.h new file mode 100644 index 0000000..717bd54 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/FIRGenLowpass_no_ipp.h @@ -0,0 +1,105 @@ +#ifndef _FIRGenLowpass_H_ +#define _FIRGenLowpass_H_ +// #include "ipp/ipps.h" +#include +#include +#define FIRGenLowpassLen 256 //滤波器长度 +#define FIRGenLowpassLen2 192 //滤波器长度 + double m_dFilterCoef[FIRGenLowpassLen]; //滤波器系数 +unsigned char m_dFilterBuff[FIRGenLowpassLen*2]; +#define PI (3.1415926) +#define POINT_SIZE (1024) +//int m_nFIRPnt; //????? +double m_dFIRCoef_ptr[POINT_SIZE]; //????? +float m_fFIRCoef_ptr[POINT_SIZE]; //????? +bool GenLowpass(double rFreq, double **coef, int point,float mult) +{ + if (NULL == *coef) + { + *coef =(double *) m_fFIRCoef_ptr; + } + int nFIRPnt0 = point - 1; + float fFIRPnt0D2 = nFIRPnt0 / 2.0f; + for (int i = 0; i < point; i++) + { + m_dFIRCoef_ptr[i] = 0.54 - 0.46 * cos((2 * PI * i) / nFIRPnt0); + } + double dTheta = 2 * PI * rFreq, dCoefSum = 0; + + for (int i = 0; i < point; i++) + { + if (fFIRPnt0D2 == i) + { + m_dFIRCoef_ptr[i] *= (dTheta / PI); + } + else + { + m_dFIRCoef_ptr[i] *= (sin((i - fFIRPnt0D2) * dTheta) / ((i - fFIRPnt0D2) * PI)); + } + dCoefSum += m_dFIRCoef_ptr[i]; + } + mult /= dCoefSum; + for(int i = 0; i < point; ++i) + { + (*coef)[i] = m_dFIRCoef_ptr[i] * mult; + } + return true; +} +/***************************** +*函数名:SetFIRLpCoef +*功能描述:设置滤波器参数 +*参数名称:coef 滤波器系数 + length 滤波器系数长度 + rate 带宽采样率之比 = 带宽 / 采样率 + bitLen 量化位宽 + mult 带宽放大系数 +*返回:成功有效滤波器长度, 失败返回0 +*****************************/ +int SetFIRLpCoef2(int *coef, int length, double rate, int bitLen, double mult = 1.0) +{ + int ret = 0; + if (NULL == coef) + { + return ret; + } + double* dFIRCoef = new double[length]; + std::fill(dFIRCoef, dFIRCoef + length, 0); + if (!GenLowpass(rate * 0.5 * mult, &dFIRCoef, length, (float ) pow(2.0, bitLen - 1) - 1)) + { + ret = 0; + } + if (bitLen <= 16) + { + for (int i = 0; i < length; ++i) { + if (dFIRCoef[i] >= 0) + { + ((short *)coef)[i] = (short)(dFIRCoef[i] + 0.5); + } + else + { + ((short *)coef)[i] = (short)(dFIRCoef[i] - 0.5); + } + } + ret = length / 2; + } + else + { + for (int i = 0; i < length; ++i) { + if (dFIRCoef[i] >= 0) + { + coef[i] = (int)(dFIRCoef[i] + 0.5); + } + else + { + coef[i] = (int)(dFIRCoef[i] - 0.5); + } + } + ret = length; + } + if (dFIRCoef != nullptr) + { + delete[] dFIRCoef; + } + return ret; +} +#endif //_FIRGenLowpass_H_ diff --git a/module/dll_source_lzy/ecap/lib/cJSON.c b/module/dll_source_lzy/ecap/lib/cJSON.c new file mode 100644 index 0000000..d7c7236 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/cJSON.c @@ -0,0 +1,3164 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 18) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + item->valuestring = NULL; + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + item->string = NULL; + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +/* Note: when passing a NULL valuestring, cJSON_SetValuestring treats this as an error and return NULL */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + size_t v1_len; + size_t v2_len; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + /* return NULL if the object is corrupted or valuestring is NULL */ + if (object->valuestring == NULL || valuestring == NULL) + { + return NULL; + } + + v1_len = strlen(valuestring); + v2_len = strlen(object->valuestring); + + if (v1_len <= v2_len) + { + /* strcpy does not handle overlapping string: [X1, X2] [Y1, Y2] => X2 < Y1 or Y2 < X1 */ + if (!( valuestring + v1_len < object->valuestring || object->valuestring + v2_len < valuestring )) + { + return NULL; + } + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + output = NULL; + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + if (printed != NULL) + { + hooks->deallocate(printed); + printed = NULL; + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + p.buffer = NULL; + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + if (cannot_access_at_index(input_buffer, 1)) + { + goto fail; /* nothing comes after the comma */ + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL) || (item != parent->child && item->prev == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0 || newitem == NULL) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + if (after_inserted != array->child && after_inserted->prev == NULL) { + /* return false if after_inserted is a corrupted array item */ + return false; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse); + +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + return cJSON_Duplicate_rec(item, 0, recurse ); +} + +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + if(depth >= CJSON_CIRCULAR_LIMIT) { + goto fail; + } + newchild = cJSON_Duplicate_rec(child, depth + 1, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); + object = NULL; +} diff --git a/module/dll_source_lzy/ecap/lib/cJSON.h b/module/dll_source_lzy/ecap/lib/cJSON.h new file mode 100644 index 0000000..37520bb --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/cJSON.h @@ -0,0 +1,306 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 18 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* Limits the length of circular references can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_CIRCULAR_LIMIT +#define CJSON_CIRCULAR_LIMIT 10000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/module/dll_source_lzy/ecap/lib/device_uhd_c.cpp b/module/dll_source_lzy/ecap/lib/device_uhd_c.cpp new file mode 100644 index 0000000..74caa02 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/device_uhd_c.cpp @@ -0,0 +1,1531 @@ + +#include "device_uhd_c.h" +#include "hulc/DataOutputFormats.h" + +#include "CtrlSpecialDecode.h" +#include "_AlgmDemodulate.h" +#include "cJSON.h" +#include "device_uhd_cpp.h" +#include +#include +#include +#include + +rx_data_cbk_t s_cbk; + +typedef struct config_struct_t +{ + char cwd[PATH_MAX]; + char dirpath[PATH_MAX]; + double rx_gain_val = 0; + char rx_clock_ref[50] = {0}; + + char config_name[255]; // = "lib9002config.json"; + char config_path[PATH_MAX]; + char is_save_adsb; + char is_save_yuanshi_iq; + uint64_t save_yuanshi_size_MByte; + uint64_t save_over_byte; + char is_save_gps; + ofstream ofs_adsb; + ofstream ofs_yuanshi_iq; + ofstream ofs_gps; + +} config_struct_t; + +config_struct_t cfg; + +#include +#include +#include +#include +int32_t init9002(void **phandle) +{ + + // + { + memset(cfg.cwd, 0, sizeof(cfg.cwd)); + strcpy(cfg.rx_clock_ref, "external"); + memset(cfg.config_name, 0, sizeof(cfg.config_name)); + memset(cfg.config_path, 0, sizeof(cfg.config_path)); + cfg.rx_gain_val = 30.0; + cfg.is_save_adsb = 0; + cfg.is_save_gps = 0; + cfg.is_save_yuanshi_iq = 0; + cfg.save_yuanshi_size_MByte = 0; + cfg.save_over_byte = 0; + } + Device_UHD *pthis = new Device_UHD(); + // getcwd(cwd, sizeof(cwd) - 1); + strcpy(cfg.config_name, "lib9002config.json"); + int len = readlink("/proc/self/exe", cfg.cwd, sizeof(cfg.cwd) - 1); + cfg.cwd[len] = '\0'; + char *dir = dirname(cfg.cwd); + + strcpy(cfg.dirpath, dir); + cout << "cfg.dirpath = " << cfg.dirpath << endl; + sprintf(cfg.config_path, "%s/%s", dir, cfg.config_name); + printf("config_path = %s\n", cfg.config_path); + + ifstream ifs(cfg.config_path); + string str; + + if (ifs.is_open()) + { + std::stringstream ss; + ss << ifs.rdbuf(); + cout << "read str = " << ss.str() << endl; + str = ss.str(); + ifs.close(); + } + + else + { + ofstream ofs(cfg.config_path); + string tmp_str = "{\"gain\":\"30.0\",\"clock_ref\":\"external\",\"is_save_adsb\":\"0\",\"is_save_yuanshi_iq\":\"0\",\"save_yuanshi_size_MByte\":\"0\",\"is_save_gps\":\"0\"}"; + ofs.write(tmp_str.c_str(), tmp_str.size()); + str = tmp_str; + ofs.close(); + } + + cout << "str = " << str << endl; + + cJSON *root = nullptr; + if (str.size() > 2) + { + root = cJSON_Parse(str.data()); + } + + cJSON *obj_gain = nullptr; + obj_gain = cJSON_GetObjectItem(root, "gain"); + if (obj_gain != nullptr) + { + char *value = obj_gain->valuestring; + // cout << "value " << value << endl; + cfg.rx_gain_val = std::stof(value); + } + else + { + cJSON_AddItemToObject(root, "gain", cJSON_CreateString("30.0")); + } + + cJSON *obj_clock_ref = nullptr; + obj_clock_ref = cJSON_GetObjectItem(root, "clock_ref"); + if (obj_clock_ref != nullptr) + { + char *value = obj_clock_ref->valuestring; + // cout << "value " << value << endl; + strcpy(cfg.rx_clock_ref, value); + } + else + { + cJSON_AddItemToObject(root, "clock_ref", cJSON_CreateString("external")); + } + + cJSON *obj_is_save_adsb = nullptr; + obj_is_save_adsb = cJSON_GetObjectItem(root, "is_save_adsb"); + if (obj_is_save_adsb != nullptr) + { + char *value = obj_is_save_adsb->valuestring; + // cout << "value " << value << endl; + cfg.is_save_adsb = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "is_save_adsb", cJSON_CreateString("0")); + } + + cJSON *obj_is_save_yuanshi_iq = nullptr; + obj_is_save_yuanshi_iq = cJSON_GetObjectItem(root, "is_save_yuanshi_iq"); + if (obj_is_save_adsb != nullptr) + { + char *value = obj_is_save_yuanshi_iq->valuestring; + // cout << "value " << value << endl; + cfg.is_save_yuanshi_iq = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "is_save_yuanshi_iq", cJSON_CreateString("0")); + } + + cJSON *obj_save_yuanshi_size_MByte = nullptr; + obj_save_yuanshi_size_MByte = cJSON_GetObjectItem(root, "save_yuanshi_size_MByte"); + if (obj_save_yuanshi_size_MByte != nullptr) + { + char *value = obj_save_yuanshi_size_MByte->valuestring; + // cout << "value " << value << endl; + cfg.save_yuanshi_size_MByte = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "save_yuanshi_size_MByte", cJSON_CreateString("0")); + } + // is_save_gps + + cJSON *obj_is_save_gps = nullptr; + obj_is_save_gps = cJSON_GetObjectItem(root, "is_save_gps"); + if (obj_is_save_gps != nullptr) + { + char *value = obj_is_save_gps->valuestring; + // cout << "value " << value << endl; + cfg.is_save_gps = std::stod(value); + } + else + { + cJSON_AddItemToObject(root, "is_save_gps", cJSON_CreateString("0")); + } + char *rootstr = cJSON_Print(root); + + cout << "rootstr" << rootstr << endl; + ofstream ofs(cfg.config_path); + if (ofs.is_open()) + { + ofs.write(rootstr, strlen(rootstr)); + ofs.close(); + } + free(rootstr); + + cJSON_Delete(root); + + cout << "config_gain_val = " << cfg.rx_gain_val << endl; + cout << "rx_clock_ref = " << cfg.rx_clock_ref << endl; + cout << "config_path" << cfg.config_path << endl; + cout << "is_save_adsb" << (int)cfg.is_save_adsb << endl; + cout << "is_save_gps" << (int)cfg.is_save_gps << endl; + cout << "is_save_yuanshi_iq" << (int)cfg.is_save_yuanshi_iq << endl; + cout << "save_yuanshi_size_MByte" << (int)cfg.save_yuanshi_size_MByte << endl; + + if (cfg.is_save_adsb) + { + string adsb_path; + + time_t t = time(0); + char t_str[30]; + memset(t_str, 0, 30); + strftime(t_str, sizeof(t_str), "%Y_%m_%d_%H_%M_%S", localtime(&t)); + + adsb_path.append(cfg.dirpath); + adsb_path.append("/adsb").append(t_str).append(".txt"); + cout << " adsb_path= " << adsb_path << endl; + cfg.ofs_adsb.open(adsb_path); + } + if (cfg.is_save_gps) + { + string gps_path; + + time_t t = time(0); + char t_str[30]; + memset(t_str, 0, 30); + strftime(t_str, sizeof(t_str), "%Y_%m_%d_%H_%M_%S", localtime(&t)); + + gps_path.append(cfg.dirpath); + gps_path.append("/gps").append(t_str).append(".txt"); + + cfg.ofs_gps.open(gps_path); + } + + if (cfg.is_save_yuanshi_iq && cfg.save_yuanshi_size_MByte > 0) + { + string yuanshi_path; + + time_t t = time(0); + char t_str[30]; + memset(t_str, 0, 30); + strftime(t_str, sizeof(t_str), "%Y_%m_%d_%H_%M_%S", localtime(&t)); + + yuanshi_path.append(cfg.dirpath); + yuanshi_path.append("/yuanshi").append(t_str).append(".dat"); + + cfg.ofs_yuanshi_iq.open(yuanshi_path, ios::binary); + } + + *phandle = pthis; + return 0; +} +int32_t free9002(void **phandle) +{ + if (phandle != nullptr) + { + Device_UHD *pthis = (Device_UHD *)*phandle; + delete pthis; + pthis = nullptr; + phandle = nullptr; + + return 0; + } + return 0; +} +int32_t isDevOpened(void *phandle, int32_t *result) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *result = (int32_t)pthis->isDevOpened(); + return 0; + } + return -1; +} +int32_t findDevices(void *phandle, uhd::device_addrs_t *addrs) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *addrs = pthis->findDevices(); + return 0; + } + return -1; +} +int32_t openDevice(void *phandle, uhd::device_addr_t addr) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->openDevice(addr); + return 0; + } + return -1; +} +int32_t closeDevice(void *phandle) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->closeDevice(); + return 0; + } + return -1; +} +int32_t findDevInfos(void *phandle, char infos[1000]) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->findDevInfos(); + strcpy(infos, str.c_str()); + return 0; + } + return -1; +} +int32_t findDevInfos2(void *phandle, char infos[1000]) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->findDevInfos2(); + strcpy(infos, str.c_str()); + return 0; + } + return -1; +} +int32_t getDevTree(void *phandle, char tree_infos[1000]) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->getDevTree(); + strcpy(tree_infos, str.c_str()); + return 0; + } + return -1; +} +int32_t setDevClockSource(void *phandle, char sourceStr[1000]) +{ + + if (phandle) + { + + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevClockSource(sourceStr); + return 0; + } + return -1; +} +int32_t getDevClockSource(void *phandle, char sourceStr[1000]) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->getDevClockSource(); + strcpy(sourceStr, str.c_str()); + return 0; + } + return -1; +} +int32_t setDevInClock(void *phandle, double clockFreq) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevInClock(clockFreq); + return 0; + } + return -1; +} +int32_t getDevInClockRange(void *phandle, double *min, double *max) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->getDevInClockRange(); + *min = pthis->getDevParamMin(); + *max = pthis->getDevParamMax(); + return 0; + } + return -1; +} +int32_t getDevInClock(void *phandle, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevInClock(); + return 0; + } + return -1; +} +int32_t getDevRXChCount(void *phandle, size_t *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevRXChCount(); + return 0; + } + return -1; +} +int32_t getDevTXChCount(void *phandle, size_t *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevTXChCount(); + return 0; + } + return -1; +} +int32_t setDevParam(void *phandle, char chStr[50], UHD_ParamType paramType, double paramValue) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevParam(chStr, paramType, paramValue); + return 0; + } + return -1; +} +int32_t getDevParam(void *phandle, char chStr[50], UHD_ParamType paramType, double *paramValue) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *paramValue = pthis->getDevParam(chStr, paramType); + return 0; + } + return -1; +} +int32_t getDevParamMin(void *phandle, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevParamMin(); + return 0; + } + return -1; +} +int32_t getDevParamMax(void *phandle, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevParamMax(); + return 0; + } + return -1; +} +int32_t setAllAntenna(void *phandle, int32_t *result) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *result = pthis->setAllAntenna(); + return 0; + } + return -1; +} +int32_t setDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int jcqData) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevDDCParam_Any(chStr, jcqOffset, jcqData); + return 0; + } + return -1; +} +int32_t getDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int *jcqData) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + unsigned int t = 0; + pthis->getDevDDCParam_Any(chStr, jcqOffset, t); + *jcqData = t; + return 0; + } + return -1; +} +int32_t setDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int bSwitch) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + + pthis->setDevDataSwitch(chStr, dataType, ZDCh, bSwitch); + return 0; + } + return -1; +} +int32_t getDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int *bSwitch) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + bool b = false; + pthis->getDevDataSwitch(chStr, dataType, ZDCh, b); + *bSwitch = b; + return 0; + } + return -1; +} +int32_t setDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long centerfreq, int bw, int sample) +{ + + if (phandle) + { + cout << "chstr:" << chStr << " centerfreq:" << centerfreq << " bw:" << bw << " sample:" << sample << endl; + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevZDParam(chStr, ZDCh, centerfreq, bw, sample); + + return 0; + } + return -1; +} +int32_t getDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long *centerfreq, int *bw, int *sample) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + long long c = 0; + int b = 0; + int s = 0; + pthis->getDevZDParam(chStr, ZDCh, c, b, s); + + *centerfreq = c; + *bw = b; + *sample = s; + return 0; + } + return -1; +} +int32_t getDevZDCenterRange(void *phandle, char chStr[50], long long &min, long long &max) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->getDevZDCenterRange(chStr, min, max); + return 0; + } + return -1; +} +int32_t getDevZDBandWidthRange(void *phandle, char chStr[50], int &min, int &max) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->getDevZDBandWidthRange(chStr, min, max); + return 0; + } + return -1; +} +int32_t getDevZDSampleFreqRange(void *phandle, char chStr[50], int arr[13], int *valid_len) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + vector v = pthis->getDevZDSampleFreqRange(chStr); + *valid_len = v.size(); + for (int i = 0; i < v.size(); ++i) + { + arr[i] = v[i]; + } + return 0; + } + return -1; +} +int32_t setDevFFTParam(void *phandle, UHD_FFTWin fftWin, UHD_FFTLen fftLen, int fftPeriod_ms) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setDevFFTParam(fftWin, fftLen, fftPeriod_ms); + return 0; + } + return -1; +} +int32_t getDevFFTParam(void *phandle, UHD_FFTWin *fftWin, UHD_FFTLen *fftLen, int *fftPeriod_ms) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + UHD_FFTWin _fftWin; + UHD_FFTLen _fftLen; + int _fftPeriod_ms; + pthis->getDevFFTParam(_fftWin, _fftLen, _fftPeriod_ms); + *fftWin = _fftWin; + *fftLen = _fftLen; + *fftPeriod_ms = _fftPeriod_ms; + return 0; + } + return -1; +} +int32_t getDevRecvSpeed(void *phandle, int channel, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevSendSpeed(channel); + return 0; + } + return -1; +} +int32_t isDevRecving(void *phandle, int channel, int *result) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *result = pthis->isDevRecving(channel); + return 0; + } + return -1; +} + +class A +{ + public: + void callBackData_Func(int channel, void *pData, int iLength) { s_cbk(channel, pData, iLength); } +}; +A a; +int32_t startDevRecv(void *phandle, int channel, rx_data_cbk_t rx_cbk) +{ + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + + s_cbk = rx_cbk; + + pthis->callBackData = std::bind(&A::callBackData_Func, &a, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + // pthis->callBackData = rx_cbk; + + pthis->startDevRecv(channel); + return 0; + } + return -1; +} +int32_t stopDevRecv(void *phandle, int channel) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->stopDevRecv(channel); + return 0; + } + return -1; +} +int32_t getDevSendSpeed(void *phandle, int channel, double *val) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *val = pthis->getDevSendSpeed(channel); + return 0; + } + return -1; +} +int32_t isDevSending(void *phandle, int channel, int32_t *result) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + *result = (int32_t)pthis->isDevSending(channel); + + return 0; + } + return -1; +} +int32_t startDevSend(void *phandle, int channel, unsigned long long send_cf, unsigned long long data_sf, char *filename, int bwhile) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->startDevSend(channel, send_cf, data_sf, filename, bwhile); + return 0; + } + return -1; +} +int32_t stopDevSend(void *phandle, int channel) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->stopDevSend(channel); + return 0; + } + return -1; +} +int32_t getGPSInfo(void *phandle, char gps_info[50]) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + string str = pthis->getGPSInfo(); + + strcpy(gps_info, str.c_str()); + return 0; + } + return -1; +} +int32_t setLedEnable(void *phandle, int led_num, int bEnable) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + pthis->setLedEnable(led_num, bEnable); + return 0; + } + return -1; +} +int32_t getLedEnable(void *phandle, int led_num, int *bEnable) +{ + + if (phandle) + { + Device_UHD *pthis = (Device_UHD *)phandle; + bool b = false; + pthis->getLedEnable(led_num, b); + *bEnable = b; + return 0; + } + return -1; +} + +#include +#include +typedef struct context_t +{ + public: + void *pdev; + adsb_msg_cbk_t adsb_cbk; + thread *t_getgps; + + bool b_pool_over; + void *adsb_pusr; + +} context_t; +unsigned long long msgNo = 0; +void *phandle_math_3a_decode = nullptr; +int lastframindex = 0; +iq_cbk_t iq_cbk; +void *iq_pusr; + +#include +int BCDToDec(int bcd, int byt) +{ + if (1 == byt) + { + return bcd - (bcd >> 4) * 6; + } + else + { + int nDec = 0; + for (int i = 0; i < byt; i++) + { + uint8_t ucBcd = (uint8_t)(bcd >> (8 * i)); + nDec += ((ucBcd - (ucBcd >> 4) * 6) * (int)std::pow(100, i)); + } + return nDec; + } +} + +time_t get_2000_1_1_000_time_t() +{ + struct tm tt; + tt.tm_year = 2000 - 1900; + tt.tm_mon = 0; + tt.tm_mday = 1; + tt.tm_hour = 0; + tt.tm_min = 0; + tt.tm_sec = 0; + return mktime(&tt); +} + +int flag1 = 0; +int is_leap_year(int year) +{ + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); +} +void day_of_year_to_month_day(int year, int day_of_year, int *month, int *day) +{ + int days_per_month[] = { + 31, // 1 月 大 + 28, // 2 月 平 + 31, // 3 月 大 + 30, // 4 月 小 + 31, // 5 月 大 + 30, // 6 月 小 + 31, // 7 月 大 + 31, // 8 月 大 + 30, // 9 月 小 + 31, // 10月 大 + 30, // 11月 小 + 31 // 12月 大 + }; + if (is_leap_year(year)) + { + days_per_month[1] = 29; + } + *month = 1; + *day = day_of_year; + for (int i = 0; i < 12 && *day > days_per_month[i]; ++i) + { + *day -= days_per_month[i]; + (*month)++; + } +} + +// uint64_t rev_cbk_lenght_per_s = 0; +// time_t recv_time_t0 = time(0); + +void _rx_data_cbk_t(int channel, void *pdata, int length) +{ + if (iq_cbk != NULL && iq_pusr != NULL) + { + iq_cbk(iq_pusr, pdata, length); + } + // if (phandle_math_3a_decode) + // { + + // int nErr = SpecDecdInput(phandle_math_3a_decode, ((char *)pdata + 64), 2048, 0); + // int a = 0; + // } + + UHD_Struct_KDDC *p = (UHD_Struct_KDDC *)pdata; + + if (p->DataType != zd_ddc) + { + return; + } + cfg.save_over_byte += length; + int mbyte = cfg.save_over_byte / (1024 * 1024); + + if (cfg.is_save_yuanshi_iq && cfg.save_yuanshi_size_MByte > 0 && cfg.ofs_yuanshi_iq.is_open() && (mbyte < (cfg.save_yuanshi_size_MByte))) + { + cfg.ofs_yuanshi_iq.write((char *)pdata, length); + return; + } + + struct_DataTime *dt = &p->DateTm; + struct tm tt2; + memset(&tt2, 0, sizeof(struct tm)); + uint64_t result_nsec = 0; + + // std::cout <<" dt_type"<<(int)dt->TimeType<TimeType) + { + case Enum_TimeType_Invlid: { + result_nsec = 0; + } + break; + case Enum_TimeType_IRIG_B: { + int B_Year = BCDToDec(dt->TimeInfo.IRIGB_Head.Year, 1) + 2000; + int B_Day = BCDToDec(dt->TimeInfo.IRIGB_Head.Day, 2); + int B_Hour = BCDToDec(dt->TimeInfo.IRIGB_Head.Hour, 1); + int B_Minute = BCDToDec(dt->TimeInfo.IRIGB_Head.Minute, 1); + int B_Second = BCDToDec(dt->TimeInfo.IRIGB_Head.Second, 1); + uint32_t B_NSec = dt->TimeInfo.IRIGB_Head.NanoSec; + + tm my_tm; + int month; + int day; + day_of_year_to_month_day(B_Year, B_Day, &month, &day); + + tt2.tm_year = B_Year - 1900; + tt2.tm_mon = month - 1; + tt2.tm_mday = day; + tt2.tm_hour = B_Hour; + tt2.tm_min = B_Minute; + tt2.tm_sec = B_Second; + + // std::cout << " y1=" << tt2.tm_year; + // std::cout << " M1=" << tt2.tm_mon; + // std::cout << " D1=" << tt2.tm_mday; + // std::cout << " H1=" << tt2.tm_hour; + // std::cout << " m1=" << tt2.tm_min; + // std::cout << " s1=" << tt2.tm_sec << endl; + // std::cout << " nsec" << B_NSec<< endl; + + time_t t2 = mktime(&tt2); + // t2+= dt->TimeInfo.RMC_Head.NanoSec; + result_nsec = t2 * 1000000000 + B_NSec; + } + break; + case Enum_TimeType_RMC: { + + // time_t t1 = get_2000_1_1_000_time_t(); + + tt2.tm_year = dt->TimeInfo.RMC_Head.Year + 2000 - 1900; + tt2.tm_mon = dt->TimeInfo.RMC_Head.Month - 1; + tt2.tm_mday = dt->TimeInfo.RMC_Head.Day; + tt2.tm_hour = dt->TimeInfo.RMC_Head.Hour; + tt2.tm_min = dt->TimeInfo.RMC_Head.Minute; + tt2.tm_sec = dt->TimeInfo.RMC_Head.Second; + + time_t t2 = mktime(&tt2); + // t2+= dt->TimeInfo.RMC_Head.NanoSec; + result_nsec = t2 * 1000000000 + dt->TimeInfo.RMC_Head.NanoSec + (8ull * 3600 * 1000000000ull); + + if (flag1 == 0) + { + // std::cout << "\n dt_type" << (int)dt->TimeType << std::endl; + + // std::cout << " y1=" << tt2.tm_year; + // std::cout << " M1=" << tt2.tm_mon; + // std::cout << " D1=" << tt2.tm_mday; + // std::cout << " H1=" << tt2.tm_hour; + // std::cout << " m1=" << tt2.tm_min; + // std::cout << " s1=" << tt2.tm_sec << endl; + // std::cout << " nsec" << tt2.tm_sec << endl; + + // std::cout << "result_nsec" << result_nsec << std::endl; + // std::cout << "this is for input" << endl; + + flag1 = 1; + } + } + break; + case Enum_TimeType_ABSCNTTM: { + uint64_t sec_n = ((uint64_t)dt->TimeInfo.ABSCNTTM_Head.Second) * 1000000000; + result_nsec = dt->TimeInfo.ABSCNTTM_Head.NanoSec + sec_n; + } + break; + case Enum_TimeType_REFCNTTM: { + uint64_t sec_n = ((uint64_t)dt->TimeInfo.REFCNTTM_Head.Second) * 1000000000; + result_nsec = dt->TimeInfo.REFCNTTM_Head.NanoSec + sec_n; + } + break; + + default: + break; + } + + // uint64_t rev_cbk_times = 0; + // time_t recv_time_t0 = time(0); + // time_t recv_time_t1 = time(0); + + // rev_cbk_lenght_per_s+=length; + // double cha_time = difftime(recv_time_t1,recv_time_t0); + // if(cha_time>1) + // { + // cout<<" recv_cbk_leng /s = "< 0) + { + // if (p->DataType == UHD_Enum_DataType::zd_ddc) + { + int cha = p->FrameIndex - lastframindex; + if (cha != 1 && cha != -65535) + { + cout << " lose frame" << " current_index" << p->FrameIndex << " lastframindex" << lastframindex << endl; + } + lastframindex = p->FrameIndex; + + // save_sata_cache.insert(save_sata_cache.end(), static_cast(pdata), static_cast(pdata) + length); + + if (phandle_math_3a_decode) + { + + // std::cout<<"result_nsec "< +// fstream g_fsTst2("test_time.txt",ios::app); +// fstream g_fsTst1("test_for_server.txt",ios::app); +time_t mesc_to_time_t(int64_t mesc) +{ + auto sec = std::chrono::duration_cast(std::chrono::milliseconds(mesc)); + auto time_point = std::chrono::system_clock::time_point(sec); + return std::chrono::system_clock::to_time_t(time_point); +} +void SpecDecdRetClbkFun(void *hdl, int taskId, MSpecDecdSigType sigType, char *data, int length) +{ + context_t *pthis = (context_t *)hdl; + USpecDecdRet *pRet = (USpecDecdRet *)data; + char cRet[1024] = {0}; + char cRet2[1024] = {0}; + switch (sigType) + { + + case MSpecDecdSigType::ADSB: { + time_t u64Time = pRet->ADSBRet.Time / 1000000000; + time_t l_tm_t = u64Time + 8 * 3600; + + tm TmTime = *(gmtime(&l_tm_t)); + TmTime.tm_year += 1900; + TmTime.tm_mon += 1; + TmTime.tm_hour; + + // // TODO:测试用 + // { + // unsigned long long ullSec = ((TmTime.tm_hour * 60) + TmTime.tm_min) * 60 + TmTime.tm_sec; + // unsigned long long ullNSec = pRet->ADSBRet.Time % 1000000000; + // unsigned long long ullTime = ((ullSec & 0x3FFFF)) << 30 | (ullNSec & 0x3FFFFFFF); + // char cTime[6] = {0}; + // memcpy(cTime, &ullTime, 6); + // for (int i = 0, j = 5; i < j; i++, j--) //小端转大端 + // { + // char cTemp = cTime[i]; + // cTime[i] = cTime[j]; + // cTime[j] = cTemp; + // } + // std::string strMsg = ""; + // for (int i = 0; i < 6; i++) + // { + // char cByte[16] = {0}; + // sprintf(cByte, " %02X", cTime[i] & 0xFF); + // strMsg += cByte; + // } + // //测试用 + // char cMsg[256] = {0}; + // sprintf(cMsg, "%s\n", strMsg.c_str()); + // // g_fsTst2.write(cMsg, strlen(cMsg)); + // // g_fsTst2.flush(); + // } + + if (flag2 == 0) + { + + // std::cout << "y2=" << TmTime.tm_year; + // std::cout << " M2=" << TmTime.tm_mon; + // std::cout << " D2=" << TmTime.tm_mday; + // std::cout << " H2=" << TmTime.tm_hour; + // std::cout << " m2=" << TmTime.tm_min; + // std::cout << " s2=" << TmTime.tm_sec << endl; + + // std::cout << "SpecDecdRetClbkFun " + // << " TmTime.tm_hour" << TmTime.tm_hour << " pRet->ADSBRet.Time" << pRet->ADSBRet.Time << " u64Time" << u64Time << std::endl; + flag2 = 1; + } + int nMsgLen = 0; + for (int i = sizeof(pRet->ADSBRet.Msg) - 1; i >= 0; i--) + { + if (0 != pRet->ADSBRet.Msg[i]) + { + nMsgLen = i + 1; + break; + } + } + if (nMsgLen >= 28) + { + nMsgLen = 28; + } + else if (nMsgLen >= 14) + { + nMsgLen = 14; + } + else + { + nMsgLen = 4; + } + // char cMsg[1024] = {0}; + // for (int i = 0; i < nMsgLen; i++) + // { + // sprintf(&cMsg[i*2], "%02x", pRet->ADSBRet.Msg[i]); + // } + // sprintf(cRet, "#%03d 0x%06x %08s %02d Lon:%f Lat:%f Alt:%f %s, %04d-%02d-%02d %02d:%02d:%02d\r", + // ++msgNo, pRet->ADSBRet.ICAO, pRet->ADSBRet.FltNo, pRet->ADSBRet.Label, pRet->ADSBRet.Longitude, pRet->ADSBRet.Latitude, pRet->ADSBRet.Altitude, cMsg, + // TmTime.tm_year, TmTime.tm_mon, TmTime.tm_mday, TmTime.tm_hour, TmTime.tm_min, TmTime.tm_sec); + // 按规定格式输出报文 + std::string strMsg = "1A"; + switch (nMsgLen / 2) + { + case 2: + strMsg += " 31"; + break; + case 7: + strMsg += " 32"; + break; + case 14: + strMsg += " 33"; + break; + default: + break; + } + unsigned long long ullSec = ((TmTime.tm_hour * 60) + TmTime.tm_min) * 60 + TmTime.tm_sec; + // if (ullSec > 86400) //86400 = 24*60*60 + // { + // return; + // } + unsigned long long ullNSec = pRet->ADSBRet.Time % 1000000000; + unsigned long long ullTime = ((ullSec & 0x3FFFF) << 30) | (ullNSec & 0x3FFFFFFF); + + char cTime[6] = {0}; + memcpy(cTime, &ullTime, 6); + for (int i = 0, j = 5; i < j; i++, j--) // 小端转大端 + { + char cTemp = cTime[i]; + cTime[i] = cTime[j]; + cTime[j] = cTemp; + } + for (int i = 0; i < 6; i++) + { + if (26 == cTime[i]) + { + strMsg += " 1A"; + } + char cByte[16] = {0}; + sprintf(cByte, " %02X", cTime[i] & 0xFF); + strMsg += cByte; + } + int nRSSI = (int)(pRet->ADSBRet.RSSI + 0.5); + if (26 == (nRSSI & 0xFF)) + { + strMsg += " 1A"; + } + char cRSSI[16] = {0}; + sprintf(cRSSI, " %02X", nRSSI & 0xFF); + strMsg += cRSSI; + for (int i = 0; i < nMsgLen / 2; i++) + { + if ('1' == pRet->ADSBRet.Msg[2 * i] && ('a' == pRet->ADSBRet.Msg[2 * i + 1] || 'A' == pRet->ADSBRet.Msg[2 * i + 1])) + { + strMsg += " 1A"; + } + char cByte[8] = {0}; + sprintf(cByte, " %C%C", toupper(pRet->ADSBRet.Msg[2 * i]), toupper(pRet->ADSBRet.Msg[2 * i + 1])); + strMsg += cByte; + } + sprintf(cRet, "%s", strMsg.c_str()); + } + + break; + + default: + break; + } + if (strlen(cRet) > 0) + { + // printf(cRet); + // printf("\n"); + } + + // //TODO:测试用 + // { + // char cMsg[256] = {0}; + // sprintf(cMsg, "%s\r", cRet); + // g_fsTst1.write(cMsg, strlen(cMsg)); + // g_fsTst1.flush(); + // } + + if (cfg.is_save_adsb && cfg.ofs_adsb.is_open()) + { + cfg.ofs_adsb << cRet; + } + pthis->adsb_cbk(pthis->adsb_pusr, cRet, strlen(cRet)); +} + +int32_t ex_declare init_adsb9002(void **phandle, adsb_msg_cbk_t cbk, void *pusr) +{ + context_t *pthis = new context_t(); + pthis->adsb_cbk = cbk; + pthis->adsb_pusr = pusr; + phandle_math_3a_decode = 0; + pthis->pdev = nullptr; + init9002(&pthis->pdev); + if (!pthis->pdev) + { + cout << "pdev init error" << endl; + return -1; + } + uhd::device_addrs_t s; + + if (findDevices(pthis->pdev, &s) != 0) + { + cout << " findDevices error!" << endl; + return -1; + } + else + { + cout << "findDevices success!" << endl; + } + if (openDevice(pthis->pdev, s[0]) != 0) + { + cout << " openDevice error!" << endl; + return -1; + } + else + { + cout << "openDevice success!" << endl; + } + if (startDevRecv(pthis->pdev, 0, _rx_data_cbk_t) != 0) + { + cout << " startDevRecv error!" << endl; + return -1; + } + else + { + cout << "startDevRecv success!" << endl; + } + + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::ZDDCSwitch, 0, false); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::KFFTSwitch, 0, false); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::KDDCSwitch, 0, false); + + setDevParam(pthis->pdev, "RX0", UHD_ParamType::CenterFreq, 1089000000); + setDevParam(pthis->pdev, "RX0", UHD_ParamType::BandWidth, 6000000); + setDevParam(pthis->pdev, "RX0", UHD_ParamType::SampleFreq, 24000000); + setDevParam(pthis->pdev, "RX0", UHD_ParamType::Gain, cfg.rx_gain_val); + + setDevZDParam(pthis->pdev, "RX0", 0, 1090000000, 3000000, 12000000); + + // setDevClockSource(pthis->pdev,"internal"); + setDevClockSource(pthis->pdev, cfg.rx_clock_ref); + cout << " REF = " << cfg.rx_clock_ref << endl; + // setDevClockSource(pthis->pdev,"external"); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::KDDCSwitch, 0, true); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::ZDDCSwitch, 0, true); + cout << " set 9002 genneric success!" << endl; + + int nFs = 12000000; + // cout <<" aaa"<b_pool_over = false; + pthis->t_getgps = new thread( + [=](context_t *ppthis) { + time_t t1 = time(0); + int print_times = 0; + while (ppthis->b_pool_over == false) + { + + time_t t2 = time(0); + if (difftime(t2, t1) > 2) + { + t1 = t2; + char gpsinfo[50]; + memset(gpsinfo, 0, 50); + + getGPSInfo(ppthis->pdev, gpsinfo); + string gpsinfostr = gpsinfo; + if (gpsinfostr.size() <= 0) + { + continue; + } + // cout <<" gpsinfo.size()"<adsb_cbk(pthis->adsb_pusr, c, len); + if (cfg.is_save_gps && cfg.ofs_gps.is_open()) + { + cfg.ofs_gps << "经度: " << longitude << "°" << "\t纬度: " << latitude << "°" << "\tbin_str:" << bin.c_str() << endl; + } + } + } + usleep(500000); + } + }, + pthis); + int a = 1; + *phandle = pthis; + + return 0; +} + +int32_t ex_declare free_adsb9002(void **phandle) +{ + context_t *pthis = (context_t *)*phandle; + if (pthis->pdev) + { + int32_t result = 0; + isDevOpened(pthis->pdev, &result); + if (result > 0) + { + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::ZDDCSwitch, 0, 0); + setDevDataSwitch(pthis->pdev, "RX0", UHD_ParamType::KDDCSwitch, 0, 0); + stopDevRecv(pthis->pdev, 0); + closeDevice(pthis->pdev); + if (pthis->t_getgps->joinable()) + { + pthis->b_pool_over = true; + pthis->t_getgps->join(); + } + pthis->t_getgps = nullptr; + } + free9002(&pthis->pdev); + } + pthis->pdev = nullptr; + SpecDecdFree(&phandle_math_3a_decode); + if (cfg.ofs_adsb.is_open()) + { + cfg.ofs_adsb.close(); + } + if (cfg.ofs_gps.is_open()) + { + cfg.ofs_gps.close(); + } + if (cfg.ofs_yuanshi_iq.is_open()) + { + cfg.ofs_yuanshi_iq.close(); + } + return 0; +} + +int ex_declare set_iq_cbk(void *phandle, iq_cbk_t recv_cbk, void *pusr) +{ + if (phandle) + { + context_t *pthis = (context_t *)phandle; + iq_cbk = recv_cbk; + iq_pusr = pusr; + return 0; + } + return -1; +} + +int ex_declare set_less_30Mhz_ddc_param(void *phandle, uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw) +{ + if (phandle) + { + context_t *pthis = (context_t *)phandle; + + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KFFTSwitch, 0, false); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KDDCSwitch, 0, false); + setDevParam(pthis->pdev, "RX1", UHD_ParamType::CenterFreq, cf_hz); + setDevParam(pthis->pdev, "RX1", UHD_ParamType::BandWidth, bw_hz); + setDevParam(pthis->pdev, "RX1", UHD_ParamType::SampleFreq, sf_hz); + setDevParam(pthis->pdev, "RX1", UHD_ParamType::Gain, cfg.rx_gain_val); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KFFTSwitch, 0, true); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KDDCSwitch, 0, true); + + return 0; + } + return -1; +} + +int ex_declare set_less_30Mhz_fft_param(void *phandle, int fftWin, int fftLen, int fftPeriod_ms, int8_t sw) +{ + if (phandle) + { + context_t *pthis = (context_t *)phandle; + + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KFFTSwitch, 0, false); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KDDCSwitch, 0, false); + + setDevFFTParam(pthis->pdev, (UHD_FFTWin)fftWin, (UHD_FFTLen)fftLen, fftPeriod_ms); + + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KFFTSwitch, 0, true); + setDevDataSwitch(pthis->pdev, "RX1", UHD_ParamType::KDDCSwitch, 0, true); + return 0; + } + return -1; +} + +// wyc code +struct Global_Read_Buffer +{ + Global_Read_Buffer(); + ~Global_Read_Buffer(); + std::string read() + { + std::lock_guard g(mtx); + std::string ret; + std::swap(ret, buffer); + return ret; + } + void append(std::string buf) + { + std::lock_guard g(mtx); + buffer.append(buf); + } + + + void *phandle{}; + std::mutex mtx; + std::string buffer; +}; + +Global_Read_Buffer t; + +void adsb2(void *pusr, char *pmsg, int msglen) +{ + t.append(std::string(pmsg, msglen)); +} + +Global_Read_Buffer::Global_Read_Buffer() +{ + init_adsb9002(&phandle, adsb2, nullptr); +} + +Global_Read_Buffer::~Global_Read_Buffer() +{ + free_adsb9002(&phandle); +} + +std::string adsb_read() +{ + // return t.read(); + return t.read(); +} + +#include +void adsb_read(char *buf, std::uint16_t *len) +{ + auto s = t.read(); + *len = s.size(); + std::memcpy(buf, s.c_str(), *len); +} + +int adsb_set_iq_cbk(iq_cbk_t recv_cbk, void* pusr) { + return set_iq_cbk(t.phandle, recv_cbk, pusr); +} + +int adsb_set_less_30Mhz_ddc_param(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw) { + return set_less_30Mhz_ddc_param(t.phandle, cf_hz, sf_hz, bw_hz, sw); +} + +int adsb_set_less_30Mhz_fft_param(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw) { + return set_less_30Mhz_fft_param(t.phandle, fftWin, fftLen, fftPeriod_ms, sw); +} diff --git a/module/dll_source_lzy/ecap/lib/device_uhd_c.h b/module/dll_source_lzy/ecap/lib/device_uhd_c.h new file mode 100644 index 0000000..39dbe9d --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/device_uhd_c.h @@ -0,0 +1,97 @@ +#ifndef _DEVICE_UHD_C_H_ +#define _DEVICE_UHD_C_H_ + +#include "makelib_common.h" +#include "device_uhd_struct.h" +// #include "device_uhd_cpp.h" +#include + + + + + +typedef void (*rx_data_cbk_t)(int channel, void *pdata, int length); + +extern "C" +{ + int32_t ex_declare init9002(void **phandle); + int32_t ex_declare free9002(void **phandle); + int32_t ex_declare isDevOpened(void *phandle, int32_t *result); + int32_t ex_declare findDevices(void *phandle, uhd::device_addrs_t *addrs); + int32_t ex_declare openDevice(void *phandle,uhd::device_addr_t addr); + int32_t ex_declare closeDevice(void *phandle); + int32_t ex_declare findDevInfos(void *phandle, char infos[1000]); + int32_t ex_declare findDevInfos2(void *phandle, char infos[1000]); + int32_t ex_declare getDevTree(void *phandle, char tree_infos[1000]); + int32_t ex_declare setDevClockSource(void *phandle, char sourceStr[1000]); + int32_t ex_declare getDevClockSource(void *phandle, char sourceStr[1000]); + int32_t ex_declare setDevInClock(void *phandle, double clockFreq); + int32_t ex_declare getDevInClockRange(void *phandle, double *min, double *max); + int32_t ex_declare getDevInClock(void *phandle, double *val); + int32_t ex_declare getDevRXChCount(void *phandle, size_t *val); + int32_t ex_declare getDevTXChCount(void *phandle, size_t *val); + int32_t ex_declare setDevParam(void *phandle, char chStr[50], UHD_ParamType paramType, double paramValue); + int32_t ex_declare getDevParam(void *phandle, char chStr[50], UHD_ParamType paramType,double *paramValue); + int32_t ex_declare getDevParamMin(void *phandle, double *val); + int32_t ex_declare getDevParamMax(void *phandle, double *val); + int32_t ex_declare setAllAntenna(void *phandle, int32_t *result); + int32_t ex_declare setDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int jcqData); + int32_t ex_declare getDevDDCParam_Any(void *phandle, char chStr[50], unsigned int jcqOffset, unsigned int *jcqData); + int32_t ex_declare setDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int bSwitch); + int32_t ex_declare getDevDataSwitch(void *phandle, char chStr[50], UHD_ParamType dataType, unsigned char ZDCh, int *bSwitch); + int32_t ex_declare setDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long centerfreq, int bw, int sample); + int32_t ex_declare getDevZDParam(void *phandle, char chStr[50], unsigned char ZDCh, long long *centerfreq, int *bw, int *sample); + int32_t ex_declare getDevZDCenterRange(void *phandle, char chStr[50], long long &min, long long &max); + int32_t ex_declare getDevZDBandWidthRange(void *phandle, char chStr[50], int &min, int &max); + int32_t ex_declare getDevZDSampleFreqRange(void *phandle, char chStr[50], int arr[13], int *valid_len); + int32_t ex_declare setDevFFTParam(void *phandle, UHD_FFTWin fftWin, UHD_FFTLen fftLen, int fftPeriod_ms); + int32_t ex_declare getDevFFTParam(void *phandle, UHD_FFTWin *fftWin, UHD_FFTLen *fftLen, int *fftPeriod_ms); + int32_t ex_declare getDevRecvSpeed(void *phandle, int channel, double *val); + int32_t ex_declare isDevRecving(void *phandle,int channel, int32_t *result); + int32_t ex_declare startDevRecv(void *phandle, int channel, rx_data_cbk_t rx_cbk); + int32_t ex_declare stopDevRecv(void *phandle, int channel); + int32_t ex_declare getDevSendSpeed(void *phandle, int channel, double *val); + int32_t ex_declare isDevSending(void *phandle, int channel, int32_t *rusult); + int32_t ex_declare startDevSend(void *phandle, int channel, unsigned long long send_cf, unsigned long long data_sf, char *filename, int bwhile); + int32_t ex_declare stopDevSend(void *phandle, int channel); + int32_t ex_declare getGPSInfo(void *phandle, char gps_info[50]); + int32_t ex_declare setLedEnable(void *phandle, int led_num, int bEnable); + int32_t ex_declare getLedEnable(void *phandle,int led_num, int *bEnable); + + + + + typedef void(*adsb_msg_cbk_t)(void *pusr,char *pmsg,int msglen); + + int32_t ex_declare init_adsb9002(void **phandle,adsb_msg_cbk_t cbk,void *pusr); + int32_t ex_declare free_adsb9002(void **phandle); + + + + // 新接口 :时间 20251107 , + //TODO: 1 增加 dma 回调函数 iq 的(ddc,ffc) +} + +#include +#include "device_uhd_c.h" +#include +#include + + +extern "C" +{ + + ex_declare void adsb_read(char* buf, std::uint16_t* len); + + typedef void (*iq_cbk_t)(void* phandle, void* pdata, uint64_t length); + typedef int (*adsb_set_iq_cbk_t)(iq_cbk_t recv_cbk, void* pusr); + typedef int (*adsb_set_less_30Mhz_ddc_param_t)(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + typedef int (*adsb_set_less_30Mhz_fft_param_t)(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw); + ex_declare int adsb_set_iq_cbk(iq_cbk_t recv_cbk, void* pusr); + ex_declare int adsb_set_less_30Mhz_ddc_param(uint64_t cf_hz, uint64_t sf_hz, uint64_t bw_hz, int8_t sw); + ex_declare int adsb_set_less_30Mhz_fft_param(uint64_t fftWin, uint64_t fftLen, int fftPeriod_ms, int8_t sw); + +} + + +#endif //_DEVICE_UHD_C_H_ \ No newline at end of file diff --git a/module/dll_source_lzy/ecap/lib/device_uhd_cpp.cpp b/module/dll_source_lzy/ecap/lib/device_uhd_cpp.cpp new file mode 100644 index 0000000..f95471f --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/device_uhd_cpp.cpp @@ -0,0 +1,2152 @@ +#include "device_uhd_cpp.h" +// #include "UHD_Probe.h" +#include "DDCSampTable.h" +#include "FIRGenLowpass_no_ipp.h" +#include "device_uhd_struct.h" +#include +#include +#include +#include +#include + +using namespace uhd; + +#define addr_dev_ref_time 0x4200 // "参考时钟" b:0101 GPS ; b:1010 外参考 ; b:1001 内参考 +#define addr_dev_version 0x420c // "设备版本" +#define addr_rx_data_sw 0x4210 //"数据接收开关" +#define addr_dev_led 0x421c +#define addr_rx_kd_fft_sw 0x4220 // fft 开关 +#define addr_rx_kd_fft_len 0x4224 // fft 长度 1k,2k,4k,8k,16k,32k +#define addr_rx_kd_fft_type 0x4228 // fft 类型 0jxc,1hnc,2hmc,3blackman +#define addr_rx_kd_fft_preiod 0x422c // fft 周期 1-1000 ms +#define addr_rx_kd_ddc_data_sw 0x4240 // 宽带数据开关 +#define addr_rx_kd_ddc_ch 0x4264 // 宽带通道号 +#define addr_rx_kd_ddc_fc 0x4270 //"过滤系数" +#define addr_rx_kd_ddc_ch_sw 0x4208 //"中频开关" 切换通道 接收模块 1:9002模块 0:30M模块 +#define addr_rx_kd_kz_speed 0x4214 // 快照速度 +#define addr_rx_ch0_30Mhz_cw 0x4278 // 接收30Mhz 控制字 +#define addr_rx_ch1_30MHz_cw 0x427c // 接收30Mhz 控制字 +#define addr_rx_kd_ch0_ddc_sf 0x4244 //"采样率" +#define addr_rx_kd_ch0_ddc_bw 0x4248 //"带宽" +#define addr_rx_kd_ch0_ddc_cf_h16 0x424c //"中心频率 高16" +#define addr_rx_kd_ch0_ddc_cf_l32 0x4250 //"中心频率 低32" +#define addr_rx_kd_ch0_ddc_df 0x4268 //"采样因子/抽取率因子" +#define addr_rx_kd_ch0_kz_sw 0x4230 // 快照开关 +#define addr_rx_kd_ch0_kz_pack_size 0x4234 // 快照包大小 +#define addr_rx_kd_ch1_ddc_sf 0x4254 //"采样率" +#define addr_rx_kd_ch1_ddc_bw 0x4258 //"带宽" +#define addr_rx_kd_ch1_ddc_cf_h16 0x425c //"中心频率 高16" +#define addr_rx_kd_ch1_ddc_cf_l32 0x4260 //"中心频率 低32" +#define addr_rx_kd_ch1_ddc_df 0x426c //"采样因子/抽取率因子" +#define addr_rx_kd_ch1_kz_sw 0x4238 // 快照开关 +#define addr_rx_kd_ch1_kz_pack_size 0x423c // 快照包大小 +#define addr_rx_zd_ch 0x4280 // 窄带通道号 +#define addr_rx_zd_ddc_data_sw 0x4284 // 窄带数据开关 +#define addr_rx_zd_df 0x4288 // 窄带抽取倍数 +#define addr_rx_zd_bw 0x428c // 窄带带宽 +#define addr_rx_zd_cw 0x4290 // 窄带控制字 +#define addr_rx_zd_fc 0x4294 // 窄带过滤系数 +#define addr_rx_zd_sf 0x4298 // 窄带采样率 +#define addr_tx_send_data_cw 0x42c0 // 发送总开关 +#define addr_tx_send_ch_sw 0x42c4 // 发送通道选择 +#define addr_tx_ch0_insert_rate 0x42c8 // 插值率 +#define addr_tx_ch1_insert_rate 0x42cc // 插值率 +#define addr_tx_ch0_30MHz_cw 0x42d0 // 发送30Mhz 控制字 +#define addr_tx_ch1_30MHz_cw 0x42d4 // 发送30Mhz 控制字 +#define addr_tm_set_h 0x42e0 // 计时高位 +#define addr_tm_set_l 0x42e4 // 计时低位 +//----------------------------------------------------------------------------------------- +#define my_iio_rx_block_point_size_big (2112 * 512) // #define my_iio_block_point_size (2112*4) +#define my_iio_block_point_size_little (2112 * 512) +#define my_iio_tx_block_point_size (2112 * 512) +#define ach_sf_count_max_hz (15.36 * 1000 * 1000) +#define rx_kz_speed_hz (1.62 * 1000 * 1000) +// #define rx_net_speed_max (500*1024*1024) +#define ZDExtraMin 0 // 窄带抽取倍数的序号,最小 +#define ZDExtraMax 12 // 窄带抽取倍数的序号,最大 +#define kz_sum ((double)(491520000 / 2)) +#define GHz (1000 * 1000 * 1000.0) +#define MHz (1000 * 1000.0) + +static unsigned long long kd_sf_all = 0; +static unsigned long long sf_all = 0; +static unsigned long long tmp_block_size = 0; +static unsigned long long tmp_sf_all = 0; +typedef struct ThreadRecvParam +{ + int channel; + void *pThis; +} ThreadRecvParam; +void addr_val_mix(int64_t addr, int64_t val, std::string &sstr) +{ + sstr.clear(); + char str[50]; + for (int i = 0; i < 50; ++i) + { + str[i] = 0; + } + sprintf(str, "%08x%08x", (unsigned int)addr, (unsigned int)val); + sstr = str; +} +bool set_esun_rx_addr_val(iio_context *ctx, int64_t addr, int64_t val) +{ + if (ctx) + { + struct iio_device *phy; + struct iio_channel *chan; + phy = iio_context_find_device(ctx, "adrv9002-phy"); + chan = iio_device_find_channel(phy, "voltage0", false); + + // cout <<" addr = 0x"<bFindedDevice = true; + dev_list.push_back(t); + p_usrp.ctx_isopen = false; + return dev_list; +} +bool Device_UHD::openDevice(uhd::device_addr_t addr) +{ + bool bRet = false; + if (this->bFindedDevice) + { + try + { + this->devAddr = addr; + uhd::usrp::multi_usrp::sptr tmp_URSP; + tmp_URSP.ctx = iio_create_context_from_uri("ip:172.22.0.2"); + // tmp_URSP.ctx = iio_create_context_from_uri("ip:192.168.1.141"); + if (tmp_URSP.ctx != NULL) + { + p_usrp = tmp_URSP; + p_usrp.ctx_isopen = true; + bRet = true; + spThread_Recv[0] = NULL; + spThread_Recv[1] = NULL; + // memset(&rxKDParam, 0, sizeof(rxKDParam)); + for (int i = 0; i < 2; i++) + { + rxKDParam[i].KCH = i; + rxKDParam[i].CenterFreq = 0; + rxKDParam[i].BandWidth = 0; /*getDevParam("RX" + i, UHD_ParamType::BandWidth);*/ + rxKDParam[i].SampleFreq = 0; /* getDevParam("RX" + i, UHD_ParamType::SampleFreq);*/ + for (int y = 0; y < ZDChMax; y++) + { + rxKDParam[i].ZDParam[y].KCH = i; + rxKDParam[i].ZDParam[y].ZCH = i * ZDChMax + y; + } + rxKDParam[i].gain = 0; + rxKDParam[i].Snapshot_Switch = false; + rxKDParam[i].Snapshot_tm_s = 0; + txKDParam[i].gain = -40; + led_val = 0; + } + kd_centerfreq_sw = 0; + fftWin = UHD_FFTWin::blkmc; + fftLen = UHD_FFTLen::_32768; + fftPeriod = 10; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + p_usrp.ctx_isopen = false; + bRet = false; + } + } + return bRet; +} +void Device_UHD::closeDevice() +{ + if (p_usrp.ctx != NULL) + { + stopDevRecv(0); + stopDevRecv(1); + stopDevSend(0); + stopDevSend(1); + // 释放前,需把所有引用都赋值NULL,方可释放 + // pTree = NULL; // + iio_context_destroy(p_usrp.ctx); + p_usrp.ctx = NULL; // 内部会自动释放指针 + p_usrp.ctx_isopen = false; + } +} +bool Device_UHD::isDevOpened() const +{ + bool ret = p_usrp.ctx_isopen; + return ret; +} +string GetUHDTree(const uhd::fs_path &path, uhd::property_tree::sptr tree) +{ + return "GetUHDTree\r\n"; +} +string Device_UHD::getDevTree() +{ + return "getDevTree\r\n"; +} +string Device_UHD::findDevInfos() +{ + string ret; + char tmp[200]; + if (p_usrp.ctx != NULL) + { + try + { + { // 查询设备信息 + ret += "ip:172.22.0.2\r\n"; + } + { // 查看设备数量、名称、时钟源 + snprintf(tmp, 200, "get_num_mboards:%d", 0); + ret += string(tmp) + "\r\n"; + ret += "\r\n"; + } + { + } + { // 查询接收通道参数 + // int num = (int)pUSRP->get_rx_num_channels(); + snprintf(tmp, 200, "get_rx_num_channels:%d", 2); + ret += string(tmp) + "\r\n"; + for (int i = 0; i < 2; i++) + { + } + ret += "\r\n"; + } + { // 查询发射通道参数 + snprintf(tmp, 200, "get_tx_num_channels:%d", 2); + ret += string(tmp) + "\r\n"; + for (int i = 0; i < 2; i++) + { + { // 查询发射频率范围 + snprintf(tmp, 200, "get_tx_freq_range(%d):%f~%f", i, 0, 6 * GHz); + ret += string(tmp) + "\r\n"; + } + } + ret += "\r\n"; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return "findDevInfos\r\n"; +} +string Device_UHD::findDevInfos2() +{ + return "ok"; +} +size_t Device_UHD::getDevRXChCount() +{ + size_t bRet = 0; + try + { + if (p_usrp.ctx != NULL) + { + bRet = 2; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return bRet; +} +size_t Device_UHD::getDevTXChCount() +{ + size_t bRet = 0; + try + { + if (p_usrp.ctx != NULL) + { + bRet = 2; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return bRet; +} +bool Device_UHD::setDevClockSource(string sourceStr) +{ + std::cout << sourceStr << std::endl; + bool ret = true; + try + { + if (p_usrp.ctx != NULL) + { + if (sourceStr.compare("gpsdo") == 0) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_dev_ref_time, 5); + } + else if (sourceStr.compare("external") == 0) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_dev_ref_time, 10); + } + else if (sourceStr.compare("internal") == 0) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_dev_ref_time, 9); + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + ret = false; + } + return ret; +} +string Device_UHD::getDevClockSource() +{ + string ret = "internal"; + try + { + if (p_usrp.ctx != NULL) + { + int64_t val; + get_esun_rx_addr_val(p_usrp.ctx, (int64_t)addr_dev_ref_time, val); + if (val == 5) + { + ret = string("gpsdo"); + } + else if (val == 9) + { + ret = string("internal"); + } + else if (val == 10) + { + ret = string("external"); + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return ret; +} +bool Device_UHD::setDevInClock(double clockFreq) +{ + bool ret = true; + return ret; +} +void Device_UHD::getDevInClockRange() +{ + try + { + if (p_usrp.ctx != NULL) + { + fParamMin = 10000000; + fParamMax = 10000000; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } +} +double Device_UHD::getDevInClock() +{ + double ret = 0; + try + { + if (p_usrp.ctx != NULL) + { + ret = 10000000; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return ret; +} +bool Device_UHD::setDevParam(string chStr, UHD_ParamType paramType, double paramValue) +{ + cout << "chstr" << chStr << " paramType:" << (int)paramType << " paramValue" << paramValue << endl; + bool ret = true; + try + { + if (p_usrp.ctx != NULL) + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + bool bTX = chStr.find("TX") != string::npos; + bool tmp_zd_switch = 0; + + //"频点", "带宽", "采样率", "增益", "校准电平" + + if (!bTX) + { + for (int i = 0; i < 8; ++i) + { + tmp_zd_switch += rxKDParam[channel].ZDParam[i].bSwitch_ZDDC; + } + switch (paramType) + { + case UHD_ParamType::CenterFreq: + // if (tmp_zd_switch) + // { + // return false; + // } + if (!bTX) + { + rxKDParam[channel].old_cf = rxKDParam[channel].CenterFreq; + rxKDParam[channel].CenterFreq = paramValue; + if (rxKDParam[channel].old_cf == 0) + { + rxKDParam[channel].old_cf = rxKDParam[channel].CenterFreq; + } + } + break; + case UHD_ParamType::BandWidth: + // if (tmp_zd_switch) + // { + // return false; + // } + if (!bTX) + { + rxKDParam[channel].old_bw = rxKDParam[channel].BandWidth; + rxKDParam[channel].BandWidth = paramValue; + if (rxKDParam[channel].old_bw == 0) + { + rxKDParam[channel].old_bw = rxKDParam[channel].BandWidth; + } + } + break; + case UHD_ParamType::SampleFreq: + // if (tmp_zd_switch) + // { + // return false; + // } + if (!bTX) + { + if (rxKDParam[channel].bSwitch_KDDC == true) + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + unsigned long long pre_cnt = sfcnt - rxKDParam[channel].SampleFreq + paramValue; + if (pre_cnt > ach_sf_count_max_hz + 1) + { + return false; + } + sf_all = pre_cnt; + } + rxKDParam[channel].old_sf = rxKDParam[channel].SampleFreq; + rxKDParam[channel].SampleFreq = paramValue; + if (rxKDParam[channel].old_sf == 0) + { + rxKDParam[channel].old_sf = rxKDParam[channel].SampleFreq; + } + } + break; + case UHD_ParamType::Gain: + if (!bTX) + { + + struct iio_device *phy; + struct iio_channel *chn = nullptr; + phy = iio_context_find_device(this->p_usrp.ctx, "adrv9002-phy"); + if (channel == 0) + { + chn = iio_device_find_channel(phy, "voltage0", false); + } + else if (channel == 1) + { + chn = iio_device_find_channel(phy, "voltage1", false); + } + int retno = iio_channel_attr_write_double(chn, "hardwaregain", paramValue); + if (retno != 0) + { + ret = false; + } + else + { + ret = true; + rxKDParam[channel].gain = paramValue; + } + } + return false; + break; + case UHD_ParamType::PowerRef: + break; + case UHD_ParamType::SnapshotSwitch: + if (!bTX) + { + bool bSwitch = (bool)paramValue; + if (bSwitch == true && rxKDParam[channel].Snapshot_Switch == false) + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + unsigned long long pre_cnt = sfcnt + rx_kz_speed_hz; + if (pre_cnt > ach_sf_count_max_hz + 1) + { + return false; + } + sf_all = pre_cnt; + } + rxKDParam[channel].Snapshot_Switch = (bool)paramValue; + // 设置获取本来的宽带 通道开关 并置为0; + unsigned char uSwitch = 0; + if (bSwitch == true) + { + rxKDParam[channel].bSwitch_KDDC = false; + for (int i = 0; i < 2; i++) + { + if (rxKDParam[i].bSwitch_KDDC) + { + uSwitch |= 0x01 << i; + } + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_data_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_data_sw, uSwitch); + } + set_esun_rx_addr_val(p_usrp.ctx, channel == 0 ? addr_rx_kd_ch0_kz_sw : addr_rx_kd_ch1_kz_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, channel == 0 ? addr_rx_kd_ch0_kz_sw : addr_rx_kd_ch1_kz_sw, rxKDParam[channel].Snapshot_Switch); + return true; + } + return false; + break; + case UHD_ParamType::Snapshot_tm_s: { + if (!bTX) + { + if (rxKDParam[channel].SampleFreq < 0) + { + return false; + } + int tmp_t_max = (int)(kz_sum / rxKDParam[channel].SampleFreq); + if (paramValue > tmp_t_max || paramValue < 0) + { + // paramValue = tmp_t_max; + rxKDParam[channel].Snapshot_tm_s = tmp_t_max; + } + else + { + rxKDParam[channel].Snapshot_tm_s = paramValue; + } + } + } + break; + defualt: + break; + } + + int kd_cur_indx = channel; + double kddcsamp = rxKDParam[channel].SampleFreq; + int ExtraPer = 0; + unsigned long long CenterFreq = rxKDParam[channel].CenterFreq; + long long BandWidth = rxKDParam[channel].BandWidth; + unsigned long long kd_centerfreq_h16 = CenterFreq & 0xffff00000000; + unsigned long long kd_centerfreq_l32 = CenterFreq & 0xffffffff; + int t = rxKDParam[channel].Snapshot_tm_s; + unsigned long long pack_size = ((unsigned int)((t * rxKDParam[channel].SampleFreq * 32.0) / (2048 * 8.0) / 16)) * 16; + set_esun_rx_addr_val(p_usrp.ctx, channel == 0 ? addr_rx_kd_ch0_kz_pack_size : addr_rx_kd_ch1_kz_pack_size, pack_size); + bool enable_6G_ch = 0; + if (CenterFreq >= 30 * MHz && CenterFreq < 6 * GHz) + { + enable_6G_ch = 1; + } + else if (CenterFreq < 30 * MHz && CenterFreq >= 0) + { + enable_6G_ch = 0; + } + if (channel == 0) + { + if (enable_6G_ch == 1) + kd_centerfreq_sw |= 0x01; + if (enable_6G_ch == 0) + kd_centerfreq_sw &= 0x02; + } + if (channel == 1) + { + if (enable_6G_ch == 1) + kd_centerfreq_sw |= 0x02; + if (enable_6G_ch == 0) + kd_centerfreq_sw &= 0x01; + } + // bool bSwitch = false; + // int kd_ddc_up = 0; + int kd_FilterCoef[128] = {0}; + for (int i = KDDCSampIndxMin0; i <= KDDCSampIndxMax0; i++) + { + double tmpssssf = (channel == 0 ?KDDCSampTab0[i][0]:KDDCSampTab1[i][0]); + if ( tmpssssf== kddcsamp) + { + ExtraPer = i; + break; + } + } + if (CenterFreq < 30 * MHz) + { + int kd_30M_ctrword = (int)((double)(CenterFreq - 15000000.0) / (channel == 0 ? define_KDDCSampleMax0 : define_KDDCSampleMax1) * pow(2, 32)); + if (kd_cur_indx == 0) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_ch0_30Mhz_cw, kd_30M_ctrword); + } + if (kd_cur_indx == 1) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_ch1_30MHz_cw, kd_30M_ctrword); + } + } + int kd_filterLen = SetFIRLpCoef2(kd_FilterCoef, 128, (double)BandWidth / kddcsamp, 18, 1.0); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_ch_sw, kd_centerfreq_sw); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_ch, kd_cur_indx); + + // if(rxKDParam[channel].old_bw != BandWidth || rxKDParam[channel].old_sf != kddcsamp) + // { + // if (kd_filterLen > 0) + // { + // for (int i = 0; i < kd_filterLen; i++) + // { + // set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_fc, kd_FilterCoef[i]); + // } + // } + // } + if (!(rxKDParam[channel].old_cf != CenterFreq && rxKDParam[channel].old_bw == BandWidth && rxKDParam[channel].old_sf == kddcsamp)) + { + if (kd_filterLen > 0) + { + for (int i = 0; i < kd_filterLen; i++) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ddc_fc, kd_FilterCoef[i]); + } + } + } + if (kd_cur_indx == 0) + { + struct iio_channel *chn = nullptr; + struct iio_device *phy; + phy = iio_context_find_device(p_usrp.ctx, "adrv9002-phy"); + chn = iio_device_find_channel(phy, "altvoltage0", true); + bool b1 = (CenterFreq >= (30 * MHz)); + bool b2 = (CenterFreq < (6 * GHz)); + if (b1 && b2) + { + iio_channel_attr_write_longlong(chn, "RX1_LO_frequency", CenterFreq); + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_df, ExtraPer); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_bw, BandWidth); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_sf, kddcsamp); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_cf_h16, kd_centerfreq_h16); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch0_ddc_cf_l32, kd_centerfreq_l32); + } + else if (kd_cur_indx == 1) + { + struct iio_channel *chn = nullptr; + struct iio_device *phy; + phy = iio_context_find_device(p_usrp.ctx, "adrv9002-phy"); + chn = iio_device_find_channel(phy, "altvoltage1", true); + if (CenterFreq >= 30 * MHz && CenterFreq < 6 * GHz) + { + iio_channel_attr_write_longlong(chn, "RX2_LO_frequency", CenterFreq); + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_df, ExtraPer); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_bw, BandWidth); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_sf, kddcsamp); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_cf_h16, kd_centerfreq_h16); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_ch1_ddc_cf_l32, kd_centerfreq_l32); + } + + struct iio_device *phy; + struct iio_channel *chn = nullptr; + phy = iio_context_find_device(this->p_usrp.ctx, "adrv9002-phy"); + if (channel == 0) + { + chn = iio_device_find_channel(phy, "voltage0", false); + } + else if (channel == 1) + { + chn = iio_device_find_channel(phy, "voltage1", false); + } + // int retno = iio_channel_attr_write_double(chn, "hardwaregain", this->rxKDParam[channel].gain); + // if (retno!=0) + // { + // ret = false; + // } + // else{ + // ret = true; + // rxKDParam[channel].gain = paramValue; + // } + } + + if (bTX) + { + if (paramType == UHD_ParamType::Gain) + { + + struct iio_device *phy; + struct iio_channel *chn = nullptr; + phy = iio_context_find_device(this->p_usrp.ctx, "adrv9002-phy"); + if (channel == 0) + { + chn = iio_device_find_channel(phy, "voltage0", true); + } + else if (channel == 1) + { + chn = iio_device_find_channel(phy, "voltage1", true); + } + int retno = iio_channel_attr_write_double(chn, "hardwaregain", paramValue); + if (retno != 0) + { + ret = false; + } + else + { + ret = true; + txKDParam[channel].gain = paramValue; + } + } + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + ret = false; + } + return ret; +} +double Device_UHD::getDevParam(string chStr, UHD_ParamType paramType) +{ + double ret; + try + { + if (p_usrp.ctx != NULL) + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + bool bTX = chStr.find("TX") != string::npos; + //"频点", "带宽", "采样率", "增益", "校准电平" + if (!bTX) + { + switch (paramType) + { + case UHD_ParamType::CenterFreq: + // ret = bTX ? p_usrp->get_tx_freq(channel) : pUSRP->get_rx_freq(channel); + if (!bTX) + { + ret = rxKDParam[channel].CenterFreq; //= ret; + } + break; + case UHD_ParamType::BandWidth: + if (!bTX) + { + ret = rxKDParam[channel].BandWidth; //= ret; + } + break; + case UHD_ParamType::SampleFreq: + if (!bTX) + { + ret = rxKDParam[channel].SampleFreq; // = ret; + } + break; + case UHD_ParamType::Gain: + ret = rxKDParam[channel].gain; + break; + case UHD_ParamType::PowerRef: + ret = 10000000; + break; + case UHD_ParamType::SnapshotSwitch: + ret = rxKDParam[channel].Snapshot_Switch; + break; + case UHD_ParamType::Snapshot_tm_s: + ret = rxKDParam[channel].Snapshot_tm_s; + } + } + if (bTX) + { + if (paramType == UHD_ParamType::Gain) + { + ret = txKDParam[channel].gain; + } + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return ret; +} +bool Device_UHD::setAllAntenna() +{ + bool ret = true; + return ret; +} +void Device_UHD::getDevParamRange(string chStr, UHD_ParamType paramType, double ¶mMin, double ¶mMax) +{ + try + { + if (p_usrp.ctx != NULL) + { + // uhd::meta_range_t range; + int channel = chStr.find("0") != string::npos ? 0 : 1; + bool bTX = chStr.find("TX") != string::npos; + //"频点", "带宽", "采样率", "增益", "校准电平" + switch (paramType) + { + case UHD_ParamType::CenterFreq: + fParamMin = 0; + fParamMax = 6 * GHz; + break; + case UHD_ParamType::BandWidth: + fParamMin = 0; + fParamMax = 40000000; + break; + case UHD_ParamType::SampleFreq: + fParamMin = 0; + fParamMax = (channel==0?define_KDDCSampleMax0:define_KDDCSampleMax1); + break; + case UHD_ParamType::Gain: + if (!bTX) + { + fParamMin = 0; + fParamMax = 34; + } + break; + case UHD_ParamType::PowerRef: + fParamMin = 0; + fParamMax = 0; + break; + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } +} +double Device_UHD::getDevParamMin() const +{ + return fParamMin; +} +double Device_UHD::getDevParamMax() const +{ + return fParamMax; +} +bool Device_UHD::setDevDDCParam_Any(string chStr, unsigned int jcqOffset, unsigned int jcqData) +{ + bool ret = true; + if (p_usrp.ctx != NULL) + { + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + set_esun_rx_addr_val(p_usrp.ctx, jcqOffset, jcqData); + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::getDevDDCParam_Any(string chStr, unsigned int jcqOffset, unsigned int &jcqData) +{ + bool ret = true; + if (p_usrp.ctx != NULL) + { + int64_t va; + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + get_esun_rx_addr_val(p_usrp.ctx, jcqOffset, va); + jcqData = va; + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::setDevDataSwitch(string chStr, UHD_ParamType dataType, unsigned char ZDCh, bool bSwitch) +{ + cout << "chstr:" << chStr << " dataType:" << (int)dataType << " ZDCh:" << ZDCh << " bSwitch:" << bSwitch << endl; + int xx = bSwitch == true ? 1 : -1; + bool ret = true; + if (p_usrp.ctx != NULL) + { + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + switch (dataType) + { + case KDDCSwitch: { + if (bSwitch != rxKDParam[channel].bSwitch_KDDC) + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + unsigned long long pre_cnt = sfcnt + (xx * rxKDParam[channel].SampleFreq); + // if (pre_cnt > ach_sf_count_max_hz + 1) + // { + // cout <<" error pre_cnt"< ach_sf_count_max_hz + 1) + { + return false; + } + sf_all = pre_cnt; + } + unsigned char uSwitch = 0; + for (int i = 0; i < 2; i++) + { + if (rxKDParam[i].bSwitch_KFFT) + { + uSwitch |= 0x01 << i; + } + } + if (bSwitch) + { + uSwitch |= (1 << channel); + } + else + { + uSwitch &= ~(1 << channel); + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_sw, uSwitch); + rxKDParam[channel].bSwitch_KFFT = bSwitch; + } + break; + case ZDDCSwitch: { + if (bSwitch != rxKDParam[channel].ZDParam[ZDCh % ZDChMax].bSwitch_ZDDC) + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + unsigned long long pre_cnt = sfcnt + xx * rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq; + // if (pre_cnt > ach_sf_count_max_hz + 1) + // { + // return false; + // } + sf_all = pre_cnt; + } + if (ZDCh >= 0 && ZDCh <= ZDChMax * 2) + { + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].bSwitch_ZDDC = bSwitch; + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ch, ZDCh); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ddc_data_sw, 0); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ch, ZDCh); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ddc_data_sw, bSwitch); + } + else + ret = false; + } + break; + default: + ret = false; + break; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::getDevDataSwitch(string chStr, UHD_ParamType dataType, unsigned char ZDCh, bool &bSwitch) +{ + bool ret = true; + if (p_usrp.ctx != NULL) + { + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + switch (dataType) + { + case KDDCSwitch: { + bSwitch = rxKDParam[channel].bSwitch_KDDC; + } + break; + case KFFTSwitch: { + bSwitch = rxKDParam[channel].bSwitch_KFFT; + } + break; + case ZDDCSwitch: { + if (ZDCh >= 0 && ZDCh <= ZDChMax * 2) + { + bSwitch = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].bSwitch_ZDDC; + } + } + break; + default: + break; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::setDevFFTParam(UHD_FFTWin fftWin, UHD_FFTLen fftLen, int fftPeriod_ms) +{ + double curfftlen = (double)fftLen; + bool ret = false; + if (p_usrp.ctx != NULL) + { + try + { + { + unsigned long long d_fft_old = 0; + unsigned long long d_fft_new = 0; + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + double t_fft_len = (double)this->fftLen; + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + d_fft_old += rxKDParam[j].bSwitch_KFFT * ((double)this->fftLen / 1024 * 2112 * (1000 / (double)this->fftPeriod) / 4); + d_fft_new += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod_ms) / 4); + sfcnt += d_fft_old; + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[j].SampleFreq * rxKDParam[j].ZDParam[j].bSwitch_ZDDC; + } + } + unsigned long long pre_cnt = sfcnt - d_fft_old + d_fft_new; + if (pre_cnt > ach_sf_count_max_hz + 1) + { + return -3; + } + sf_all = pre_cnt; + } + if (fftPeriod_ms >= 1 && fftPeriod_ms <= 1000) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_type, fftWin); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_len, fftLen); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_kd_fft_preiod, fftPeriod_ms); + this->fftWin = fftWin; + this->fftLen = fftLen; + this->fftPeriod = fftPeriod_ms; + ret = true; + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::getDevFFTParam(UHD_FFTWin &fftWin, UHD_FFTLen &fftLen, int &fftPeriod_ms) +{ + bool ret = true; + if (p_usrp.ctx != NULL) + { + fftWin = this->fftWin; + fftLen = this->fftLen; + fftPeriod_ms = this->fftPeriod; + } + return ret; +} +bool Device_UHD::setLedEnable(int led_num, bool bEnable) +{ + int mv_size = 0; + if (led_num == 0) + { + mv_size = 1; + } + if (led_num == 1) + { + mv_size = 4; + } + if (led_num == 2) + { + mv_size = 3; + } + if (led_num == 3) + { + mv_size = 2; + } + if (led_num == 4) + { + mv_size = 0; + } + if (led_num == 5) + { + mv_size = 5; + } + if (p_usrp.ctx != NULL) + { + if (bEnable) + { + this->led_val = led_val | (1 << mv_size); + } + else + { + this->led_val &= ~(1 << mv_size); + } + set_esun_rx_addr_val(p_usrp.ctx, addr_dev_led, led_val); + return true; + } + return false; +} +bool Device_UHD::getLedEnable(int led_num, bool &bEnable) +{ + if (p_usrp.ctx != NULL) + { + int mv_size = 0; + if (led_num == 0) + { + mv_size = 1; + } + if (led_num == 1) + { + mv_size = 4; + } + if (led_num == 2) + { + mv_size = 3; + } + if (led_num == 3) + { + mv_size = 2; + } + if (led_num == 4) + { + mv_size = 0; + } + if (led_num == 5) + { + mv_size = 5; + } + int enable_val = led_val >> mv_size & 0x01; + bEnable = enable_val; + return true; + } + return false; +} +int Device_UHD::setDevZDParam(string chStr, unsigned char ZDCh, long long centerfreq, int bw, int sample) +{ + int ret = 0; + if (p_usrp.ctx != NULL) + { + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + if (sample == 0) + { + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].CenterFreq = 0; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].BandWidth = 0; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq = 0; + } + else + { + int kddcSam = rxKDParam[channel].SampleFreq; + long long kddcCenter = rxKDParam[channel].CenterFreq; + int kddcBW = rxKDParam[channel].BandWidth; + int samIndex = ZDExtraMin; + for (int i = ZDExtraMin; i <= ZDExtraMax; i++) + { + if ((int)(kddcSam / pow(2, i + 0)) == sample) + { + samIndex = i; + break; + } + } + int samSum = 0; + for (auto zd : rxKDParam[channel].ZDParam) + { + samSum += zd.ZCH != ZDCh ? zd.SampleFreq : 0; + } + if (samIndex < ZDExtraMin || samSum + sample > ZDSamSum) + { + ret = -3; + } + if (centerfreq < 0 || centerfreq < kddcCenter - kddcBW / 2 || centerfreq > kddcCenter + kddcBW / 2) + { + ret = -1; + } + if (bw < (int)(kddcSam / pow(2, ZDExtraMax + 2) * 0.1) || bw > (int)(kddcSam / pow(2, ZDExtraMin + 2) * 0.8)) + { + ret = -2; + } + { + unsigned long long sfcnt = 0; + for (int j = 0; j < 2; ++j) + { + sfcnt += rxKDParam[j].SampleFreq * rxKDParam[j].bSwitch_KDDC; + sfcnt += rxKDParam[j].bSwitch_KFFT * ((double)fftLen / 1024 * 2112 * (1000 / (double)fftPeriod) / 4); + sfcnt += rxKDParam[j].Snapshot_Switch * rx_kz_speed_hz; + for (int i = 0; i < 8; ++i) + { + sfcnt += rxKDParam[j].ZDParam[i].SampleFreq * rxKDParam[j].ZDParam[i].bSwitch_ZDDC; + } + } + unsigned long long pre_cnt = sfcnt - rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq + sample; + if (pre_cnt > ach_sf_count_max_hz + 1) + { + return -3; + } + sf_all = pre_cnt; + } + if (ret == 0) + { + int freqkey = (int)((double)(centerfreq - rxKDParam[channel].CenterFreq) / kddcSam * pow(2, 32)); + long long old_cf = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].CenterFreq; + long long old_bw = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].BandWidth; + long long old_sf = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].CenterFreq = centerfreq; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].BandWidth = bw; + rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq = sample; + int zd_is_up = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].bSwitch_ZDDC; + { + int CH = ZDCh; + unsigned long long BandWidth = bw; + unsigned long long SampleFreq = sample; + unsigned long long CenterFreq = centerfreq; + int CtrWord = freqkey; + bool bSwitch = zd_is_up; + int chouqvlv_num = samIndex; + int chouqvlv = samIndex; + int zd_FilterCoef[128]; + memset(zd_FilterCoef, 0, 128 * sizeof(int)); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ch, ZDCh); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_df, chouqvlv); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_bw, BandWidth); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_cw, CtrWord); + if (!(old_cf != CenterFreq && old_bw == BandWidth && old_sf == sample)) + { + int zd_filterLen = SetFIRLpCoef2(zd_FilterCoef, 128, (double)BandWidth / SampleFreq, 18, 1.0); + for (int i = 0; i < zd_filterLen; ++i) + { + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_fc, zd_FilterCoef[i]); + } + } + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_sf, SampleFreq); + // set_esun_rx_addr_val(p_usrp.ctx, addr_rx_zd_ch, ZDCh); + } + ret = 0; + } + } + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + } + return ret; +} +bool Device_UHD::getDevZDParam(string chStr, unsigned char ZDCh, long long ¢erfreq, int &bw, int &sample) +{ + bool ret = true; + try + { + int channel = chStr.find("0") != string::npos ? 0 : 1; + centerfreq = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].CenterFreq; + bw = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].BandWidth; + sample = rxKDParam[channel].ZDParam[ZDCh % ZDChMax].SampleFreq; + } + catch (std::exception ex2) + { + std::cout << ex2.what() << std::endl; + } + return ret; +} +void Device_UHD::getDevZDCenterRange(string chStr, long long &min, long long &max) +{ + int channel = chStr.find("0") != string::npos ? 0 : 1; + min = rxKDParam[channel].CenterFreq - rxKDParam[channel].BandWidth / 2; + min = std::max(min, 0LL); + max = rxKDParam[channel].CenterFreq + rxKDParam[channel].BandWidth / 2; +} +void Device_UHD::getDevZDBandWidthRange(string chStr, int &min, int &max) +{ + int channel = chStr.find("0") != string::npos ? 0 : 1; + min = rxKDParam[channel].SampleFreq / pow(2, ZDExtraMax + 2) * 0.1; + max = rxKDParam[channel].SampleFreq / pow(2, ZDExtraMin + 2) * 0.8; +} +vector Device_UHD::getDevZDSampleFreqRange(string chStr) +{ + vector vecSam; + int channel = chStr.find("0") != string::npos ? 0 : 1; + for (int i = ZDExtraMax; i >= ZDExtraMin; i--) + { + double exSam = rxKDParam[channel].SampleFreq / pow(2, i + 2); + if (exSam > ZDSamSum) + { + continue; + } + vecSam.push_back(exSam); + } + return vecSam; +} +bool old_need_sw; +void ThreadRecv_Func(ThreadRecvParam recvParam) +{ + Device_UHD *pThis = (Device_UHD *)recvParam.pThis; + int channel = recvParam.channel; + if (pThis != NULL && pThis->p_usrp.ctx != NULL) + { + try + { + std::chrono::steady_clock::time_point befTime = std::chrono::steady_clock::now(); + long long iRecvedLen = 0; + struct iio_device *phy; + struct iio_channel *chan; + phy = iio_context_find_device(pThis->p_usrp.ctx, "adrv9002-phy"); + if (pThis->p_usrp.ctx) + { + if (channel == 0) + { + chan = iio_device_find_channel(phy, "voltage0", false); + iio_channel_attr_write(chan, "ensm_mode", "rf_enabled"); + + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + chan = iio_device_find_channel(phy, "voltage0", true); + iio_channel_attr_write(chan, "ensm_mode", "primed"); + + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + tmp_block_size = my_iio_rx_block_point_size_big; + pThis->p_usrp.iio_rx_arr[channel].rx_buf = iio_device_create_buffer(pThis->p_usrp.iio_rx_arr[channel].rx_dev, my_iio_rx_block_point_size_big, false); + } + if (channel == 1) + { + chan = iio_device_find_channel(phy, "voltage1", false); + iio_channel_attr_write(chan, "ensm_mode", "rf_enabled"); + + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx2-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + chan = iio_device_find_channel(phy, "voltage1", true); + iio_channel_attr_write(chan, "ensm_mode", "primed"); + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx2-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + tmp_block_size = my_iio_rx_block_point_size_big; + pThis->p_usrp.iio_rx_arr[channel].rx_buf = iio_device_create_buffer(pThis->p_usrp.iio_rx_arr[channel].rx_dev, my_iio_rx_block_point_size_big, false); + } + // int ttret = iio_buffer_set_blocking_mode( pThis->p_usrp.iio_rx_arr[channel].rx_buf,false); + // int b =0; + } + void *buff_ptrs; + while (pThis->bThread_Recv[channel]) + { + tmp_sf_all = sf_all; + int nrxbytes = iio_buffer_refill(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + int a = 0; + buff_ptrs = iio_buffer_start(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + size_t recvSize = nrxbytes; // num_rx_samps * iPointSize * iChCount; + if (recvSize > 0 && nrxbytes <= tmp_block_size * 4) + { + // cout <<" have data"<ischeck_head; + if (bCheckHead) + { + int &iRecvBufSize = pThis->iRecvBufSize[channel]; // 缓存总大小 + int &iRecvBufPosi = pThis->iRecvBufPosi[channel]; // 缓存当前大小 + char *pDataBuf = (char *)pThis->pRecvBuf[channel]; // 缓存数据指针 + int length = recvSize; // 网络接收数据大小 + int lengthPosi = 0; // 网络接收数据已处理大小 + char *pData = (char *)buff_ptrs; // 网络数据指针 + char *pCheckData = NULL; // 检测包头的数据指针 + int posi = 0; + if (iRecvBufPosi > 0) + { + int needSize = iRecvBufSize - iRecvBufPosi; // 还需要补充数据量 + needSize = needSize > length ? length : needSize; + lengthPosi = needSize; + memcpy((char *)pDataBuf + iRecvBufPosi, pData, needSize); + iRecvBufPosi = needSize + iRecvBufPosi; // 当前缓存中数据长度 + pCheckData = pDataBuf; + } + else + { + iRecvBufPosi = length; + pCheckData = pData; + } + // 111 + while (pThis->bThread_Recv[channel] && posi < iRecvBufPosi) + { + if (posi + 4 < iRecvBufPosi) + { + if (((unsigned int *)(pCheckData + posi))[0] == 0xf628f6f6) + { + int packLen = (int)2112; + if (posi + packLen <= iRecvBufPosi) + { + if (pThis->callBackData != NULL) + { + pThis->callBackData(channel, pCheckData + posi, packLen); + if (*((char *)pCheckData + posi + 12) == 16) + { + char gps_info[60] = {0}; + strncpy(gps_info, pCheckData + posi + 64, 28); + pThis->gps_info = gps_info; + std::reverse(pThis->gps_info.begin(), pThis->gps_info.end()); + } + } + posi += packLen; + } + else + { + break; + } + } + else + { + posi += 1; + } + } + else + { + break; + } + } + // 如果有剩余数据 + if (posi < iRecvBufPosi) + { + memcpy(pDataBuf, pCheckData + posi, iRecvBufPosi - posi); + iRecvBufPosi = iRecvBufPosi - posi; + } + else + { + iRecvBufPosi = 0; + } + if (pCheckData != pData) + { + if (length - lengthPosi < 2112) + { + memcpy(pDataBuf + iRecvBufPosi, pData + lengthPosi, length - lengthPosi); + iRecvBufPosi += length - lengthPosi; + } + else + { + // 111 + posi = lengthPosi - iRecvBufPosi; + pCheckData = pData; + while (pThis->bThread_Recv[channel] && posi < length) + { + if (posi + 4 < length) + { + if (((unsigned int *)(pCheckData + posi))[0] == 0xf628f6f6) + { + int packLen = (int)2112; + if (posi + packLen <= length) + { + if (pThis->callBackData != NULL) + { + pThis->callBackData(channel, pCheckData + posi, packLen); + if (*((char *)pCheckData + posi + 12) == 16) + { + char gps_info[60] = {0}; + strncpy(gps_info, pCheckData + posi + 64, 28); + pThis->gps_info = gps_info; + std::reverse(pThis->gps_info.begin(), pThis->gps_info.end()); + } + } + posi += packLen; + } + else + { + break; + } + } + else + { + posi += 1; + } + } + else + { + break; + } + } + // 如果有剩余数据 + if (posi < length) + { + memcpy(pDataBuf, pCheckData + posi, length - posi); + iRecvBufPosi = length - posi; + } + else + { + iRecvBufPosi = 0; + } + // 111 + } + } + } + else + { + pThis->callBackData(channel, buff_ptrs, (int)recvSize); + if (*((char *)buff_ptrs + 12) == 16) + { + char gps_info[60] = {0}; + strncpy(gps_info, (char *)buff_ptrs + 64, 28); + pThis->gps_info = gps_info; + std::reverse(pThis->gps_info.begin(), pThis->gps_info.end()); + } + } + } + + bool need_big = false; + for (int i = 0; i < 2; ++i) + { + if (pThis->rxKDParam[i].bSwitch_KDDC == true) + { + need_big = true; + } + for (int j = 0; j < 8; ++j) + { + if (pThis->rxKDParam[i].ZDParam[j].bSwitch_ZDDC == true) + { + need_big = true; + } + } + } + if (need_big == true && need_big != old_need_sw) + { + iio_buffer_destroy(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + pThis->p_usrp.iio_rx_arr[channel].rx_buf = iio_device_create_buffer(pThis->p_usrp.iio_rx_arr[channel].rx_dev, my_iio_rx_block_point_size_big, false); + old_need_sw = need_big; + } + if (need_big == false && need_big != old_need_sw) + { + iio_buffer_destroy(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + pThis->p_usrp.iio_rx_arr[channel].rx_buf = iio_device_create_buffer(pThis->p_usrp.iio_rx_arr[channel].rx_dev, my_iio_rx_block_point_size_big, false); + old_need_sw = need_big; + } + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fRecvSpeed[channel] = (double)iRecvedLen / dur_ms.count() * 1000; + iRecvedLen = 0; + befTime = curTime; + } + } + if (channel == 0) + { + chan = iio_device_find_channel(phy, "voltage0", false); + iio_channel_attr_write(chan, "ensm_mode", "primed"); + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + iio_buffer_destroy(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + } + if (channel == 1) + { + chan = iio_device_find_channel(phy, "voltage1", false); + iio_channel_attr_write(chan, "ensm_mode", "primed"); + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx2-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + iio_buffer_destroy(pThis->p_usrp.iio_rx_arr[channel].rx_buf); + } + } + catch (std::exception ex) + { + std::cout << ex.what() << std::endl; + } + } +} +double Device_UHD::getDevRecvSpeed(int channel) const +{ + return fRecvSpeed[channel]; +} +bool Device_UHD::isDevRecving(int channel) const +{ + return bThread_Recv[channel]; +} +void Device_UHD::startDevRecv(int channel) +{ + if (spThread_Recv[channel] != NULL) + { + bThread_Recv[channel] = false; + spThread_Recv[channel]->join(); + spThread_Recv[channel] = NULL; + if (pRecvBuf[channel] != NULL) + { + free(pRecvBuf[channel]); + pRecvBuf[channel] = NULL; + } + } + else + { + iRecvBufSize[channel] = 2112 * 2; + iRecvBufPosi[channel] = 0; + pRecvBuf[channel] = malloc(iRecvBufSize[channel]); + bThread_Recv[channel] = true; + ThreadRecvParam recvParam; + recvParam.channel = channel; + recvParam.pThis = this; + // rx_kd_ddc_sw |= (1 << channel); + // set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, rx_kd_ddc_sw); + + send_all_sw &= ~(1 << channel); + set_esun_rx_addr_val(p_usrp.ctx, addr_tx_send_data_cw, send_all_sw); + + long long tmll = time(0); + + set_esun_rx_addr_val(p_usrp.ctx, addr_tm_set_l, tmll & 0xffffffff); + set_esun_rx_addr_val(p_usrp.ctx, addr_tm_set_h, tmll >> 32); + + spThread_Recv[channel] = std::make_shared(ThreadRecv_Func, recvParam); + } +} +void Device_UHD::stopDevRecv(int channel) +{ + if (spThread_Recv[channel] != NULL) + { + bThread_Recv[channel] = false; + spThread_Recv[channel]->join(); + spThread_Recv[channel] = NULL; + } +} +double Device_UHD::getDevSendSpeed(int channel) const +{ + return fSendSpeed[channel]; +} +bool Device_UHD::isDevSending(int channel) const +{ + return bThread_Send[channel]; +} +void ThreadSend_Func(Device_UHD *pThis, unsigned long long send_cf, unsigned long long data_sf, int channel, char *filename, bool bWhile) +{ + if (pThis != NULL && pThis->p_usrp.ctx_isopen) + { + std::chrono::steady_clock::time_point befTime = std::chrono::steady_clock::now(); + long long iSendedLen0 = 0; + long long iSendedLen1 = 0; + char *pdata0; + char *pdata1; + struct iio_device *phy; + struct iio_channel *chan0; + struct iio_channel *chan1; + phy = iio_context_find_device(pThis->p_usrp.ctx, "adrv9002-phy"); + // 定义缓冲区 + char buffer0[1024]; + char buffer1[1024]; + std::vector content0; + std::vector content1; + size_t read_len0 = 0; + size_t read_len1 = 0; + // set_esun_rx_addr_val(p_usrp.ctx, addr_dev_ref_time, 5); + int insert_num = 0; + int cw_30Mhz = 0; + cw_30Mhz = ((double)send_cf / ((double)122880000) * pow(2, 32)); + for (int i = 0; i <= 10; i++) + { + if (((int)define_KDDCSampleMax0 / pow(2, i)) == data_sf) + { + insert_num = i; + break; + } + } + if (channel == 0) + { + pThis->send_all_sw |= (1 << channel); + } + if (channel == 1) + { + pThis->send_all_sw |= (1 << channel); + } + if (send_cf > 30 * MHz && send_cf < 6 * GHz) + { + pThis->send_chan_sw &= ~(1 << channel); + } + if (send_cf <= 30 * MHz && send_cf > 0) + { + pThis->send_chan_sw |= (1 << channel); + } + int ch_cw = pThis->send_chan_sw; + if (pThis->p_usrp.ctx) + { + + // pThis->rxKDParam[channel].bSwitch_KDDC = false; + // pThis->rxKDParam[channel].bSwitch_KFFT = false; + // for(int i = 0; i <8;++i) + // { + // pThis->rxKDParam[0].ZDParam[i].bSwitch_ZDDC = false; + + // } + + // int kz_val = 0; + // for(int i = 0; i <2;++i) + // { + // if(pThis->rxKDParam[i].Snapshot_Switch == true) + // { + + // kz_val |=1<rxKDParam[i].Snapshot_Switch + + // kz_val &=~(1<p_usrp.ctx, addr_rx_kd_ddc_data_sw, 0); + + // set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_rx_kd_ddc_data_sw, 0); + + if (send_cf > 30 * MHz && send_cf < 6 * GHz) + { + if (channel == 0) + { + + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + + chan0 = iio_device_find_channel(phy, "voltage0", false); + iio_channel_attr_write(chan0, "ensm_mode", "primed"); + + chan0 = iio_device_find_channel(phy, "voltage0", true); + iio_channel_attr_write(chan0, "ensm_mode", "rf_enabled"); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch0_insert_rate, insert_num); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + chan0 = iio_device_find_channel(phy, "altvoltage2", true); + iio_channel_attr_write_longlong(chan0, "TX1_LO_frequency", send_cf); + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + pThis->p_usrp.iio_tx_arr[channel].tx_buf = iio_device_create_buffer(pThis->p_usrp.iio_tx_arr[channel].tx_dev, my_iio_tx_block_point_size, false); + } + if (channel == 1) + { + + pThis->p_usrp.iio_rx_arr[channel].rx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-rx2-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_rx_arr[channel].rx_dev, pThis->p_usrp.iio_rx_arr[channel].rx_chan, false); + + chan1 = iio_device_find_channel(phy, "voltage1", false); + iio_channel_attr_write(chan1, "ensm_mode", "primed"); + + chan1 = iio_device_find_channel(phy, "voltage1", true); + iio_channel_attr_write(chan1, "ensm_mode", "rf_enabled"); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch1_insert_rate, insert_num); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + chan1 = iio_device_find_channel(phy, "altvoltage3", true); + iio_channel_attr_write_longlong(chan1, "TX2_LO_frequency", send_cf); + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx2-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + // pThis->p_usrp.iio_tx_arr[channel].tx_chan[1] = iio_device_find_channel(pThis->p_usrp.iio_tx_arr[channel].tx_dev, "voltage0", true); + // if (!iio_channel_is_enabled(pThis->p_usrp.iio_tx_arr[channel].tx_chan[1])) + // { + // iio_channel_enable(pThis->p_usrp.iio_tx_arr[channel].tx_chan[1]); + // } + pThis->p_usrp.iio_tx_arr[channel].tx_buf = iio_device_create_buffer(pThis->p_usrp.iio_tx_arr[channel].tx_dev, my_iio_tx_block_point_size, false); + } + } + else if (send_cf <= 30 * MHz && send_cf > 0) + { + if (channel == 0) + { + chan0 = iio_device_find_channel(phy, "voltage0", true); + iio_channel_attr_write(chan0, "ensm_mode", "primed"); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch0_insert_rate, insert_num); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch0_30MHz_cw, cw_30Mhz); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + pThis->p_usrp.iio_tx_arr[channel].tx_buf = iio_device_create_buffer(pThis->p_usrp.iio_tx_arr[channel].tx_dev, my_iio_tx_block_point_size, false); + } + if (channel == 1) + { + chan1 = iio_device_find_channel(phy, "voltage1", true); + iio_channel_attr_write(chan1, "ensm_mode", "primed"); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch1_insert_rate, insert_num); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_ch1_30MHz_cw, cw_30Mhz); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx2-lpc"); + stream_channels_get_enable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + // pThis->p_usrp.iio_tx_arr[channel].tx_chan[1] = iio_device_find_channel(pThis->p_usrp.iio_tx_arr[channel].tx_dev, "voltage0", true); + // if (!iio_channel_is_enabled(pThis->p_usrp.iio_tx_arr[channel].tx_chan[1])) + // { + // iio_channel_enable(pThis->p_usrp.iio_tx_arr[channel].tx_chan[1]); + // } + pThis->p_usrp.iio_tx_arr[channel].tx_buf = iio_device_create_buffer(pThis->p_usrp.iio_tx_arr[channel].tx_dev, my_iio_tx_block_point_size, false); + } + } + } + + if (channel == 0) + { + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) + { + std::cerr << "无法打开文件" << std::endl; + return; // 返回错误代码 + } + // 逐块读取文件内容 + while (file.read(buffer0, sizeof(buffer0))) + { + std::streamsize bytesRead = file.gcount(); // 获取实际读取的字节数 + content0.insert(content0.end(), buffer0, buffer0 + bytesRead); + } + // 处理最后一块可能不足1024字节的情况 + if (file) + { + std::streamsize bytesRead = file.gcount(); + content0.insert(content0.end(), buffer0, buffer0 + bytesRead); + } + // 关闭文件 + file.close(); + } + if (channel == 1) + { + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) + { + std::cerr << "无法打开文件" << std::endl; + return; // 返回错误代码 + } + // 逐块读取文件内容 + while (file.read(buffer1, sizeof(buffer1))) + { + std::streamsize bytesRead = file.gcount(); // 获取实际读取的字节数 + content1.insert(content1.end(), buffer1, buffer1 + bytesRead); + } + // 处理最后一块可能不足1024字节的情况 + if (file) + { + std::streamsize bytesRead = file.gcount(); + content1.insert(content1.end(), buffer1, buffer1 + bytesRead); + } + // 关闭文件 + file.close(); + } + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_data_cw, pThis->send_all_sw); + while (pThis->bThread_Send[channel]) + { + if (channel == 0) + { + size_t num_tx = iio_buffer_push(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + if (num_tx <= 0 || num_tx > my_iio_tx_block_point_size * 4) + { + continue; + } + pdata0 = (char *)iio_buffer_start(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + if (!pdata0) + { + continue; + } + if (content0.size() < num_tx) + { + break; + } + if (content0.size() - read_len0 < num_tx) + { + if (bWhile == true) + { + // read_len0 = 0; + + // 多余数据 + unsigned long long dysj = content0.size() - read_len0; + // 需要切割的头部数据 + unsigned long long need_cut_size = num_tx - dysj; + + ::memcpy(pdata0, content0.data() + read_len0, dysj); + ::memcpy(pdata0 + dysj, content0.data(), need_cut_size); + + read_len0 = need_cut_size; + iSendedLen0 += num_tx; + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fSendSpeed[channel] = (double)iSendedLen0 / dur_ms.count() * 1000; + iSendedLen0 = 0; + befTime = curTime; + } + + continue; + } + else + { + break; + } + } + ::memcpy(pdata0, content0.data() + read_len0, num_tx); + read_len0 += num_tx; + iSendedLen0 += num_tx; + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fSendSpeed[channel] = (double)iSendedLen0 / dur_ms.count() * 1000; + iSendedLen0 = 0; + befTime = curTime; + } + } + if (channel == 1) + { + size_t num_tx = iio_buffer_push(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + if (num_tx <= 0 || num_tx > my_iio_tx_block_point_size * 4) + { + continue; + } + pdata1 = (char *)iio_buffer_start(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + if (!pdata1) + { + continue; + } + if (content1.size() < num_tx) + { + break; + } + if (content1.size() - read_len1 < num_tx) + { + if (bWhile == true) + { + // read_len1 = 0; + + // 多余数据 + unsigned long long dysj = content1.size() - read_len1; + // 需要切割的头部数据 + unsigned long long need_cut_size = num_tx - dysj; + + ::memcpy(pdata1, content1.data() + read_len1, dysj); + ::memcpy(pdata1 + dysj, content1.data(), need_cut_size); + + read_len1 = need_cut_size; + iSendedLen1 += num_tx; + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fSendSpeed[channel] = (double)iSendedLen1 / dur_ms.count() * 1000; + iSendedLen1 = 0; + befTime = curTime; + } + + continue; + } + else + { + break; + } + } + ::memcpy(pdata1, content1.data() + read_len1, num_tx); + read_len1 += num_tx; + iSendedLen1 += num_tx; + std::chrono::steady_clock::time_point curTime = std::chrono::steady_clock::now(); + auto dur_ms = std::chrono::duration_cast(curTime - befTime); + if (dur_ms.count() > 1000) + { + pThis->fSendSpeed[channel] = (double)iSendedLen1 / dur_ms.count() * 1000; + iSendedLen1 = 0; + befTime = curTime; + } + } + } + + //--------------------------------------------- + + pThis->send_chan_sw &= ~(1 << channel); + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_ch_sw, ch_cw); + + pThis->send_all_sw &= ~(1 << channel); + + set_esun_rx_addr_val(pThis->p_usrp.ctx, addr_tx_send_data_cw, pThis->send_all_sw); + if (channel == 0) + { + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + chan0 = iio_device_find_channel(phy, "voltage0", true); + iio_channel_attr_write(chan0, "ensm_mode", "primed"); + } + if (channel == 1) + { + pThis->p_usrp.iio_tx_arr[channel].tx_dev = iio_context_find_device(pThis->p_usrp.ctx, "axi-adrv9002-tx2-lpc"); + stream_channels_get_disenable(pThis->p_usrp.iio_tx_arr[channel].tx_dev, pThis->p_usrp.iio_tx_arr[channel].tx_chan, true); + chan0 = iio_device_find_channel(phy, "voltage1", true); + iio_channel_attr_write(chan0, "ensm_mode", "primed"); + } + + iio_buffer_destroy(pThis->p_usrp.iio_tx_arr[channel].tx_buf); + } +} +void Device_UHD::startDevSend(int channel, unsigned long long send_cf, unsigned long long data_sf, char *filename, bool bwhile) +{ + if (spThread_Send[channel] != NULL) + { + bThread_Send[channel] = false; + spThread_Send[channel]->join(); + spThread_Send[channel] = NULL; + } + else + { + bThread_Send[channel] = true; + rx_kd_ddc_sw &= ~(1 << channel); + set_esun_rx_addr_val(p_usrp.ctx, addr_rx_data_sw, rx_kd_ddc_sw); + spThread_Send[channel] = std::make_shared(ThreadSend_Func, this, send_cf, data_sf, channel, filename, bwhile); + } +} +void Device_UHD::stopDevSend(int channel) +{ + if (spThread_Send[channel] != NULL) + { + bThread_Send[channel] = false; + spThread_Send[channel]->join(); + spThread_Send[channel] = NULL; + } +} +string Device_UHD::getGPSInfo() +{ + + return this->gps_info; +} diff --git a/module/dll_source_lzy/ecap/lib/device_uhd_cpp.h b/module/dll_source_lzy/ecap/lib/device_uhd_cpp.h new file mode 100644 index 0000000..434f7c5 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/device_uhd_cpp.h @@ -0,0 +1,113 @@ +#ifndef DEVICE_UHD_H +#define DEVICE_UHD_H + +#include "device_uhd_struct.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +class Device_UHD +{ +public: + uhd::device_addr_t devAddr; //设备地址 + bool bFindedDevice; + std::shared_ptr spThread_Recv[2]; + std::shared_ptr spThread_Send[2]; + uhd::property_tree::sptr pTree; + int kd_centerfreq_sw; + double fParamMin; + double fParamMax; + UHD_FFTWin fftWin;//窗类型 + UHD_FFTLen fftLen;//窗长度 + int fftPeriod;//频谱周期 1-1000,单位ms + UHD_KDParam rxKDParam[2];//rx宽带通道参数 + UHD_KDParam txKDParam[2];//tx宽带通道参数 + int FilterCoef[256];//滤波器系数数组 +public: + std::string gps_info; + int led_val; + +public: + //! Device type, used as a filter in make + //enum device_filter_t { ANY, USRP, CLOCK }; + bool isDevOpened() const; + uhd::device_addrs_t findDevices(); + bool openDevice(uhd::device_addr_t addr); + //关闭设备 + void closeDevice(); + //获取设备信息 + string findDevInfos(); + //获取设备信息2 + string findDevInfos2(); + //获取设备树 + string getDevTree(); + //设置时钟源 + bool setDevClockSource(string sourceStr); + //获取时钟源 + string getDevClockSource(); + bool setDevInClock(double clockFreq); + void getDevInClockRange(); + double getDevInClock(); + size_t getDevRXChCount(); + size_t getDevTXChCount(); + bool setDevParam(string chStr, UHD_ParamType paramType, double paramValue); + double getDevParam(string chStr, UHD_ParamType paramType); + void getDevParamRange(string chStr, UHD_ParamType paramType, double& paramMin, double& paramMax); + double getDevParamMin() const; + double getDevParamMax() const; + bool setAllAntenna(); + bool setDevDDCParam_Any(string chStr, unsigned int jcqOffset, unsigned int jcqData); + bool getDevDDCParam_Any(string chStr, unsigned int jcqOffset, unsigned int& jcqData); + bool setDevDataSwitch(string chStr, UHD_ParamType dataType, unsigned char ZDCh, bool bSwitch); + bool getDevDataSwitch(string chStr, UHD_ParamType dataType, unsigned char ZDCh, bool& bSwitch); + //采样率为0,表示该通道停止使用 + bool ischeck_head =true; + int setDevZDParam(string chStr, unsigned char ZDCh, long long centerfreq, int bw, int sample); + //采样率为0,表示该通道未使用 + bool getDevZDParam(string chStr, unsigned char ZDCh, long long& centerfreq, int& bw, int& sample); + void getDevZDCenterRange(string chStr, long long& min, long long& max); + void getDevZDBandWidthRange(string chStr, int& min, int& max); + vector getDevZDSampleFreqRange(string chStr); + bool setDevFFTParam(UHD_FFTWin fftWin, UHD_FFTLen fftLen, int fftPeriod_ms); + bool getDevFFTParam(UHD_FFTWin& fftWin, UHD_FFTLen& fftLen, int& fftPeriod_ms); + + bool bThread_Recv[2] = { 0, 0 }; + double fRecvSpeed[2] = { 0, 0 }; + void* pRecvBuf[2] = { 0, 0 }; + int iRecvBufSize[2] = { 0, 0 }; + int iRecvBufPosi[2] = { 0, 0 }; + double getDevRecvSpeed(int channel) const; + bool isDevRecving(int channel) const; + void startDevRecv(int channel); + void stopDevRecv(int channel); + double getDevSendSpeed(int channel) const; + bool isDevSending(int channel) const; + bool bThread_Send[2]= {0,0}; + double fSendSpeed[2] = {0,0}; + + int send_chan_sw =0; + int send_all_sw = 0; + int rx_kd_ddc_sw = 0; + + void startDevSend(int channel ,unsigned long long send_cf, unsigned long long data_sf,char*filename,bool bwhile); + void stopDevSend(int channel); + //uhd::usrp::multi_usrp::sptr pUSRP1; + uhd::usrp::multi_usrp::sptr p_usrp; + std::function callBackData; + + std::string getGPSInfo(); + bool setLedEnable(int led_num,bool bEnable); + bool getLedEnable(int led_num,bool &bEnable); + + + +}; +#endif // DEVICE_UHD_H diff --git a/module/dll_source_lzy/ecap/lib/device_uhd_struct.h b/module/dll_source_lzy/ecap/lib/device_uhd_struct.h new file mode 100644 index 0000000..f4a543d --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/device_uhd_struct.h @@ -0,0 +1,322 @@ +#ifndef device_uhd_struct_h +#define device_uhd_struct_h +#include +#include +#include +#ifdef __linux__ +#include +#include "iio.h" +#endif +#ifdef WIN32 +#include +#endif +#include + +#pragma pack(push) +#pragma pack(1) +namespace uhd +{ + typedef std::string fs_path; + class device_addr_t + { + public: + std::string uri; + std::string to_string(void) const + { + return uri; + } + }; + typedef std::vector device_addrs_t; + namespace usrp + { + namespace multi_usrp + { + typedef struct rx_parm + { + struct iio_device *rx_dev = nullptr; + struct iio_channel *rx_chan[2]; + struct iio_buffer *rx_buf; + bool rx_chan_is_enable; + bool rx_buf_is_enable; + } rx_parm; + typedef struct tx_parm + { + struct iio_device *tx_dev = nullptr; + struct iio_channel *tx_chan[2]; + struct iio_buffer *tx_buf; + bool tx_chan_is_enable; + bool tx_buf_is_enable; + } tx_parm; + typedef struct sptr + { + struct iio_context *ctx = nullptr; + bool ctx_isopen; + struct rx_parm iio_rx_arr[2]; + struct tx_parm iio_tx_arr[2]; + device_addrs_t uri_list; + std::string cur_sourceStr; + double cur_clockFreq; + int rx0_power_reference; + int rx1_power_reference; + int tx0_power_reference; + int tx1_power_reference; + } sptr; + } + } + namespace property_tree + { + typedef std::string sptr; + } +} +#pragma pack(pop) +#define ZDSamSum 200000000//8路窄带采样率之和 +#define ZDChMax 8 //每路大通道下窄带通道总数 +enum UHD_ParamType : int +{ + CenterFreq = 0, + BandWidth = 1, + SampleFreq = 2, + Gain = 3, + PowerRef = 4, + KDDCSwitch = 5, + KFFTSwitch = 6, + ZDDCSwitch = 7, + ZDDCParam = 8, + KFFTParam = 9, + ClockSource = 10, + SnapshotSwitch = 11, + Snapshot_tm_s = 12, +}; +enum UHD_FFTWin : unsigned char +{ + jxc =0,// 矩形窗 = 0, + hnc = 1,// 汉宁窗 = 1, + hmc = 2, // 海明窗 = 2, + blkmc = 3,// 布莱克曼窗 = 3, +}; +enum UHD_FFTLen : unsigned int +{ + _1024 = 1024, + _2048 = 2048, + _4096 = 4096, + _8192 = 8192, + _16384 = 16384, + _32768 = 32768, +}; +#pragma pack(push) +#pragma pack(1) +struct UHD_ZDParam +{ + unsigned char KCH = 0;//宽带通道号 + unsigned short ZCH = 0;//窄带通道号 + long long CenterFreq = 0; // 中频 + int BandWidth = 0; //带宽 + int SampleFreq = 0; // 采样率 + int ExtraPer = 1024;//抽取率 4-4096,2的指数 + bool bSwitch_ZDDC = false; + +}; +struct UHD_KDParam +{ + unsigned char KCH = 0;//宽带通道号 + long long CenterFreq = 10000000; + + int BandWidth = 160000000; + int SampleFreq = 200000000; + + bool bSwitch_6GCH = false; + + bool bSwitch_KDDC = false; + bool bSwitch_KFFT = false; + double gain = 0; + UHD_ZDParam ZDParam[ZDChMax]; + int ExtraPer; + bool Snapshot_Switch=false; + int Snapshot_tm_s = 0; + + unsigned long long old_sf; + unsigned long long old_cf; + unsigned long long old_bw; + +}; +#pragma region 枚举/结构体 + +/** @brief TODO: 时码类型枚举 + * @enum LibWZ57_Enum_TimeType + */ +enum Enum_TimeType : uint8_t +{ + Enum_TimeType_Invlid = 0, /**< 无效的*/ + Enum_TimeType_IRIG_B = 1, /**< IRIG-B*/ + Enum_TimeType_RMC = 2, /**< RMC*/ + Enum_TimeType_ABSCNTTM = 3, /**< 绝对计数时间*/ + Enum_TimeType_REFCNTTM = 4, /**< 相对计数时间*/ +}; +typedef struct struct_DateTime +{ + + Enum_TimeType TimeType; /**< 10 时码类型*/ + + /** @brief 10个字节的时码信息 + * @details + * 根据 @ref LibWZ57_Enum_TimeType TimeType,对应不同的 联合体 + */ + union TimeInfo + { + ///@brief IRIG-B + ///@struct IRIGB_Head + struct IRIGB_Head + { + uint8_t Year; /**< 年 字节1*/ + uint16_t Day; /**< 日 字节2*/ + uint8_t Hour; /**< 时 字节1*/ + uint8_t Minute; /**< 分 字节1*/ + uint8_t Second; /**< 秒 字节1*/ + uint32_t NanoSec; /**< 纳 字节4*/ + } IRIGB_Head; + + ///@brief RMC + ///@struct RMC_Head + struct RMC_Head + { + uint8_t Year; /**< 年 字节1*/ + uint8_t Month; /**< 月 字节1*/ + uint8_t Day; /**< 日 字节1*/ + uint8_t Hour; /**< 时 字节1*/ + uint8_t Minute; /**< 分 字节1*/ + uint8_t Second; /**< 秒 字节1*/ + uint32_t NanoSec; /**< 纳 字节4*/ + } RMC_Head; + + ///@brief 绝对计数时间 + ///@struct ABSCNTTM_Head + struct ABSCNTTM_Head + { + ///@brief 秒 字节6; 基准秒计数时间+相对秒计数时间 单位:秒 + uint8_t Second[6]; + ///@brief 纳秒 字节4; + uint32_t NanoSec; + } ABSCNTTM_Head; + + ///@brief 相对计数时间 + ///@struct REFCNTTM_Head + struct REFCNTTM_Head + { + ///@brief 秒 字节6; 相对秒计数时间单位:秒 + uint8_t Second[6]; + ///@brief 纳秒 字节4; + uint32_t NanoSec; + } REFCNTTM_Head; + } TimeInfo; + +} struct_DataTime; + + + +#pragma region 枚举 +/***************************** +*数据类型 +*****************************/ +enum UHD_Enum_DataType : unsigned char +{ + zd_ddc = 1, + kd_ddc = 2, + kd_fft = 3, + GPS_Info = 16, + // 窄带DDC = 0x01, + // 宽带DDC = 0x02, + // 宽带FFT = 0x03, +}; +#pragma endregion +#pragma region 结构体 +#pragma region UHD_Struct_KFFT UHD_Struct_KDDC UHD_Struct_ZDDC +///

+/// 宽带FFT头 +/// +typedef struct +{ + /// + /// 帧标识 6 0x2828F628F6F6 固定值 + /// + unsigned int Head; + /// + /// + /// + unsigned short Head2; + /// + /// 协议版本 1 0x80 + /// + unsigned char DealVersion; + /// + /// 帧头长度 1 64 + /// + unsigned char FrameHeadLen; + /// + /// 数据长度 2 2048 + /// + unsigned short DataLen; + /// + /// 有效数据长度 2 2048 + /// + unsigned short UsefullLen; + /// + /// 数据类型 1 + /// + UHD_Enum_DataType DataType; + unsigned char Other1; + /// + /// 通道号 + /// + unsigned char Channel; + unsigned char Other2; + unsigned short ZDChannel; + /// + /// 帧计数 2 0 ~ 65535 循环递增 + /// + unsigned short FrameIndex; + /// + /// 帧起止标识 1 0x0 + /// + unsigned char FrameBeginFlag; + struct_DateTime DateTm; + /// + /// 采样率 4 单位Hz + /// + unsigned int SampleFreq_Hz; + /// + /// 带宽 4 单位Hz + /// + unsigned int BandWidth_Hz; + /// + /// 中心频点 4 单位Hz,低位 + /// + unsigned int CenterFreq_Hz_Low; + /// + /// 中心频点 2 单位Hz,高位 + /// + unsigned short CenterFreq_Hz_High; + union + { + unsigned char other5[18]; + struct + { + unsigned char other4[6]; + /// + /// FFT长度 65536 + /// + int FFTPoint; + unsigned char other5[2]; + float other6; + /// + /// 段内帧号 + /// + unsigned short SubFrame; + } FFT; + } Param; + //数据 2048 共1024个数据,1个数据2字节。 +} UHD_Struct_KFFT, UHD_Struct_KDDC, UHD_Struct_ZDDC; +#pragma endregion +#pragma endregion +#pragma pack(pop) +#pragma endregion +#endif diff --git a/module/dll_source_lzy/ecap/lib/hulc/Bit.cpp b/module/dll_source_lzy/ecap/lib/hulc/Bit.cpp new file mode 100644 index 0000000..2965e78 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/Bit.cpp @@ -0,0 +1,761 @@ +#include "Bit.h" +#include +#include +#include +#include +#include +// 2025-01-02 05:01:50 [info] 1a480118 9607f81e f020 3a24 67761dbd 1a9ecbf8 5655c75b 00601307 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _USE_GTEST +#include +using namespace yC; +#endif +constexpr std::array make_hex_to_val() { + std::array t{255}; // 初始化为255 + for (uint8_t i = '0'; i <= '9'; ++i) t[i] = i - '0'; + for (uint8_t i = 'A'; i <= 'F'; ++i) t[i] = i - 'A' + 10; + for (uint8_t i = 'a'; i <= 'f'; ++i) t[i] = i - 'a' + 10; + return t; +} +constexpr std::array make_val_to_hex(bool uppercase) { + std::uint8_t s = uppercase ? 'A' : 'a'; + std::array t{}; + for (uint8_t i = 0; i < 10; ++i) { + t[i] = '0' + i; // '0' - '9' + } + for (uint8_t i = 10; i < 16; ++i) { + t[i] = s + (i - 10); // 'a' - 'f' 'A - F' + } + return t; +} +static constexpr std::array val_to_upper_hex = make_val_to_hex(true); +static constexpr std::array val_to_lower_hex = make_val_to_hex(false); +static constexpr std::array hex_to_val = make_hex_to_val(); +constexpr const char* hex_lut_lower = "0123456789abcdef"; +constexpr const char* hex_lut_upper = "0123456789ABCDEF"; +constexpr const char* big_dian_hex_to_bin[16] = { + "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", + "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111" +}; +constexpr const char* small_dian_hex_to_bin[16] = { + "0000", "1000", "0100", "1100", "0010", "1010", "0110", "1110", + "0001", "1001", "0101", "1101", "0011", "1011", "0111", "1111" +}; +constexpr std::array, 256> make_xor_table() { + std::array, 256> t{}; + for (size_t i = 0; i < 256; ++i) { + for (size_t j = 0; j < 256; ++j) { + t[i][j] = 255; + } + } + for (size_t i = 0; i < 16; ++i) { + for (size_t j = 0; j < 16; ++j) { + t[val_to_lower_hex[i]][val_to_lower_hex[j]] = i ^ j; + t[val_to_upper_hex[i]][val_to_upper_hex[j]] = i ^ j; + } + } + return t; +} + +static constexpr std::array, 256> xor_table = make_xor_table(); +inline size_t bin4_to_hex(bool big_byte_order, const char* a) { + static size_t small_base[]{1, 2, 4, 8}; + static size_t big_base[]{8, 4, 2, 1}; + auto cur_dian = big_byte_order ? big_base : small_base; + size_t ret = 0; + for (size_t i = 0; i < 4; ++i) { + auto cur = size_t(a[i] - '0'); + ret += cur_dian[i] * cur; + } + return ret; +} + + + +namespace yC { + const bool is_big_endian = []() { + uint16_t x = 0x0102; // 设置一个已知的 16-bit 值 + auto* p = reinterpret_cast(&x); + return p[0] == 0x01; // 如果 p[0] 为 0x01,说明是大端字节序 + }(); + std::vector split(const std::string& str, const std::string& delimiter) { + std::vector tokens; + size_t start = 0; + size_t end = str.find(delimiter); + if (end == std::string::npos) return {}; + while (end != std::string::npos) { + tokens.push_back(str.substr(start, end - start)); + start = end + 1; + end = str.find(delimiter, start); + } + tokens.push_back(str.substr(start)); // Add the last token + return tokens; + } + std::string bin2mem(const std::string& bin) { + const size_t len = bin.size(); + if (len % 8 != 0) { + throw std::invalid_argument("binary string length must be a multiple of 8"); + } + std::string result; + result.reserve(len / 8); // 提前分配 + for (size_t i = 0; i < len; i += 8) { + uint8_t byte = 0; + for (size_t j = 0; j < 8; ++j) { + char bit = bin[i + j]; + if (bit == '1') { + byte |= (1 << (7 - j)); + } else if (bit != '0') { + std::ostringstream oss; + oss << "Invalid bit character '" << bit << "' at position " << (i + j); + throw std::invalid_argument(oss.str()); + } + } + result.push_back(static_cast(byte)); + } + return result; + } + + void bin2mem(void* dest, const char* data, size_t size) { + if (size % 8 != 0) { + throw std::invalid_argument("Binary string length must be a multiple of 8"); + } + uint8_t* byte_dest = static_cast(dest); // 将 dest 转换为字节指针 + for (size_t i = 0; i < size; i += 8) { + uint8_t byte = 0; + for (size_t j = 0; j < 8; ++j) { + char bit = data[i + j]; + if (bit == '1') { + byte |= (1 << (7 - j)); // 设置相应的位 + } else if (bit != '0') { + std::ostringstream oss; + oss << "Invalid bit character '" << bit << "' at position " << (i + j); + throw std::invalid_argument(oss.str()); + } + } + byte_dest[i / 8] = byte; // 将转换后的字节写入目标内存 + } + } + + void hex2mem_Ex(void* dest, const char* data, size_t size) { + // 判断输入 hex 数据的长度是否为偶数 + if (size % 2 != 0) { + throw std::invalid_argument("hex string length must be even"); + } + // 填充目标数据 + for (size_t i = 0; i < size; i += 2) { + unsigned char hi = data[i]; + unsigned char lo = data[i + 1]; + auto hi_val = hex_to_val[hi]; + auto lo_val = hex_to_val[lo]; + + if (hi_val == 255 || lo_val == 255) { + std::ostringstream oss; + oss << "hex:[" << data << "] Invalid hex: [" << data[i] << data[i + 1] << "] at pos " << i; + throw std::invalid_argument(oss.str()); + } + + // 填充目标内存 + unsigned char byte = (hi_val << 4) | lo_val; + std::memcpy(static_cast(dest) + i / 2, &byte, 1); + } + } + + void mem2hex_Ex2(void* dest, void* mem, size_t num, bool uppercase, const char* middle, size_t middle_len) { + assert(middle != nullptr); + + const char* lut = uppercase ? hex_lut_upper : hex_lut_lower; + if (middle_len == std::numeric_limits::max()) { + middle_len = (middle != nullptr) ? std::strlen(middle) : 0; + } + char* dest_ptr = static_cast(dest); + for (size_t i = 0; i < num; ++i) { + unsigned char c = static_cast(mem)[i]; + *dest_ptr++ = lut[c >> 4]; + *dest_ptr++ = lut[c & 0xF]; + // 添加分隔符 + if (middle_len > 0 && i + 1 < num) { + std::copy(middle, middle + middle_len, dest_ptr); + dest_ptr += middle_len; + } + } + } + + void hex2bin_Ex(void* dest, const char* data, size_t size, bool big_dian) { + auto& dian = big_dian ? big_dian_hex_to_bin : small_dian_hex_to_bin; + for (size_t i = 0; i < size; i++) { + auto str = dian[hex_to_val[(unsigned char)(data[i])]]; + std::memmove(static_cast(dest) + i * 4, str, 4); + } + } + + void + bin2hex_Ex(void* dest, const char* d, size_t size, bool big_dian, bool uppercase) { + const char* lut = uppercase ? hex_lut_upper : hex_lut_lower; + auto tt = get_bin2hex_size(size); + auto n1 = size; + auto n2 = tt * 4; + char cur[4] = {'0', '0', '0', '0'}; + auto r = static_cast(dest); + if (!big_dian) { + // 小端 补充后导0 + for (size_t i = 0; i < n2; i++) { + auto j = i%4; + cur[j] = i >= n1 ? '0' : d[i]; + if (j == 3) { + auto val = bin4_to_hex(false, cur); + r[tt - 1 - i/4] = lut[val]; + } + } + } else { + // 大端模式:补充前导0 + for (size_t i = 0; i < n2; i++) { + // 对应位置 + auto j = i%4; + cur[j] = i >= n1 ? '0' : d[n1 - 1 - i]; + if (j == 3) { + auto val = bin4_to_hex(false, cur); + r[tt - 1 - i/4] = lut[val]; + } + } + } + } +#ifdef _USE_GTEST + TEST(Bit2223, bin2hex_Ex) { + // EXPECT_EQ(bin2hex("01101110"), "6E"); + // + { + // 0110 1110 + // 6 E + std::string it = "1101110"; + auto size = get_bin2hex_size(it.size()); + std::string res; + res.resize(size); + bin2hex_Ex(res.data(), it.c_str(), it.size(), true, true); + EXPECT_STREQ(res.c_str(), "6E"); + } + { + // 1101 1100 + // B 3 + + std::string it = "1101110"; + auto size = get_bin2hex_size(it.size()); + std::string res; + res.resize(size); + bin2hex_Ex(res.data(), it.c_str(), it.size(), false, true); + + // std::cout << res << std::endl; + EXPECT_STREQ(res.c_str(), "3B"); + } + + + } +#endif + + + void xor_bin_Ex(void* dest, const char* s1, size_t n1, const char* s2, size_t n2, bool big_dian) { + if (n1 > n2) { + std::swap(n1, n2); + std::swap(s1, s2); + } + // n1 <= n2 + char* r = static_cast(dest); + if (!big_dian) { + // 小端 补充后导0 + for (size_t i = 0; i < n1; i++) { + r[i] = s1[i] == s2[i] ? '0' : '1'; + } + for (size_t i = n1; i < n2; i++) { + r[i] = '0' == s2[i] ? '0' : '1'; + } + } else { + // 大端模式:补充前导0 + for (size_t i = 0; i < n1; i++) { + // 对应位置 + auto c1 = s1[n1 - 1 - i]; + auto c2 = s2[n2 - 1 - i]; + r[n2 - 1 - i] = c1 == c2 ? '0' : '1'; // 对应位异或 + } + for (size_t i = n1; i < n2; i++) { + auto c1 = '0'; + auto c2 = s2[n2 - 1 - i]; + r[n2 - 1 - i] = c1 == c2 ? '0' : '1'; // 对应位异或 + } + } + } +#ifdef _USE_GTEST + TEST(Bit2223, xor_bin_Ex) { + const char* s1 = "10"; + const char* s2 = "111"; + std::string res; + res.resize(3); + xor_bin_Ex(res.data(), s1, 2, s2, 3, true); + EXPECT_STREQ(res.c_str(), "101"); + xor_bin_Ex(res.data(), s1, 2, s2, 3, false); + EXPECT_STREQ(res.c_str(), "011"); + xor_bin_Ex(res.data(), s2, 3, s1, 2, false); + EXPECT_STREQ(res.c_str(), "011"); + + s2 = "1111"; + res.resize(4); + // 0010 + // 1111 + // 1101 + xor_bin_Ex(res.data(), s1, 2, s2, 4, true); + EXPECT_STREQ(res.c_str(), "1101"); + } +#endif + + + + void xor_hex_Ex(void* dest, const char* s1, size_t n1, const char* s2, size_t n2, bool big_dian, bool uppercase) { + if (n1 > n2) { + std::swap(n1, n2); + std::swap(s1, s2); + } + auto& val_to_hex = uppercase ? val_to_upper_hex : val_to_lower_hex; + // n1 <= n2 + auto r = static_cast(dest); + if (!big_dian) { + // 小端 补充后导0 + for (size_t i = 0; i < n1; i++) { + //r[i] = s1[i] == s2[i] ? '0' : '1'; + auto c1 = s1[i]; + auto c2 = s2[i]; + r[i] = val_to_hex[xor_table[c1][c2]]; + } + for (size_t i = n1; i < n2; i++) { + //r[i] = '0' == s2[i] ? '0' : '1'; + auto c1 = '0'; + auto c2 = s2[i]; + r[i] = val_to_hex[xor_table[c1][c2]]; + } + } else { + // 大端模式:补充前导0 + for (size_t i = 0; i < n1; i++) { + // 对应位置 + auto c1 = s1[n1 - 1 - i]; + auto c2 = s2[n2 - 1 - i]; + //r[n2 - 1 - i] = c1 == c2 ? '0' : '1'; // 对应位异或 + auto idx = xor_table[c1][c2]; + r[n2 - 1 - i] = val_to_hex[idx]; // 对应位异或 + } + for (size_t i = n1; i < n2; i++) { + auto c1 = '0'; + auto c2 = s2[n2 - 1 - i]; + auto idx = xor_table[c1][c2]; + //r[n2 - 1 - i] = c1 == c2 ? '0' : '1'; // 对应位异或 + r[n2 - 1 - i] = val_to_hex[idx]; // 对应位异或 + } + } + } +#ifdef _USE_GTEST + TEST(Bi222, xor_hex_Ex) { + { + const char* s1 = "10"; + const char* s2 = "111"; + std::string res; + res.resize(3); + xor_hex_Ex(res.data(), s1, 2, s2, 3, true, true); + EXPECT_STREQ(res.c_str(), "101"); + xor_hex_Ex(res.data(), s1, 2, s2, 3, false, true); + EXPECT_STREQ(res.c_str(), "011"); + xor_hex_Ex(res.data(), s2, 3, s1, 2, false, true); + EXPECT_STREQ(res.c_str(), "011"); + } + { + const char* s1 = "ab"; + const char* s2 = "111"; + std::string res; + res.resize(3); + // 0ab 0000 0110 0111 + // 111 0001 0001 0001 + // 1ba 0001 0111 0110 + xor_hex_Ex(res.data(), s1, 2, s2, 3, true, true); + EXPECT_STREQ(res.c_str(), "1BA"); + + + + } + } +#endif + + // 将字节序从大端转换到当前平台字节序,并复制到目标内存中 + void big_endian_2_platform(const std::string& big_endian, char* dest) { + // 先复制原始数据 + std::string result = big_endian; + // 如果当前平台是小端,反转数据 + if (!is_big_endian) { + std::reverse(result.begin(), result.end()); + } + // 将转换后的数据拷贝到目标内存中 + std::memcpy(dest, result.data(), result.size()); + } + std::string big_endian_2_platform(const std::string& big_endian) { + std::string result; + result.resize(big_endian.size()); + big_endian_2_platform(big_endian, result.data()); + return result; + } + // 将字节序从小端转换到当前平台字节序,并复制到目标内存中 + void little_endian_2_platform(const std::string& little_endian, char* dest) { + // 先复制原始数据 + std::string result = little_endian; + // 如果当前平台是大端,反转数据 + if (is_big_endian) { + std::reverse(result.begin(), result.end()); + } + // 将转换后的数据拷贝到目标内存中 + std::memcpy(dest, result.data(), result.size()); + } + std::string little_endian_2_platform(const std::string& little_endian) { + std::string result; + result.resize(little_endian.size()); + big_endian_2_platform(little_endian, result.data()); + return result; + } + // 将平台字节序转换为大端字节序 + std::string platform_2_big_endian(void* t, int size) { + char* memory = reinterpret_cast(t); + // for (int i = 0; i < size; ++i) { + // std::cout << (int)memory[i] << std::endl; + // } + std::string result(memory, memory + size); + // 如果当前平台是小端,反转字符串,使其成为大端字节序 + //std::cout << "platform_2_big_endian start " << memory2hex(result) << std::endl; + if (!is_big_endian) { + std::reverse(result.begin(), result.end()); + } + //std::cout << "platform_2_big_endian end " << memory2hex(result) << std::endl; + return result; + } + // 将平台字节序转换为小端字节序 + std::string platform_2_little_endian(void* t, int size) { + char* memory = reinterpret_cast(t); + std::string result(memory, memory + size); + // 如果当前平台是大端,反转字符串,使其成为小端字节序 + if (is_big_endian) { + std::reverse(result.begin(), result.end()); + } + return result; + } +#define GET_BIT(byte, pos) (((byte) >> (pos)) & 1) +#define SET_BIT(byte, pos) ((byte) |= (1 << (pos))) +#define CLR_BIT(byte, pos) ((byte) &= ~(1 << (pos))) +#define WRITE_BIT(byte, pos, val) \ + ((byte) = ((byte) & ~(1 << (pos))) | (((val) & 1) << (pos))) + template + void bit_op(void* dest, size_t d_offset, const void* src, size_t s_offset, size_t num, BitOp op) { + auto* d = (uint8_t*)dest; + const auto* s = (const uint8_t*)src; + for (size_t i = 0; i < num; ++i) { + size_t src_pos = s_offset + i; + size_t dst_pos = d_offset + i; + uint8_t src_bit = GET_BIT(s[src_pos / 8], src_pos % 8); + uint8_t dst_bit = GET_BIT(d[dst_pos / 8], dst_pos % 8); + uint8_t res_bit = op(dst_bit, src_bit); + WRITE_BIT(d[dst_pos / 8], dst_pos % 8, res_bit); + } + } + void bit_move(void* d, size_t doff, const void* s, size_t soff, size_t n) { + bit_op(d, doff, s, soff, n, [](uint8_t, uint8_t s) { return s; }); + } + void bit_xor(void* d, size_t doff, const void* s, size_t soff, size_t n) { + bit_op(d, doff, s, soff, n, [](uint8_t d, uint8_t s) { return d ^ s; }); + } + void bit_or(void* d, size_t doff, const void* s, size_t soff, size_t n) { + bit_op(d, doff, s, soff, n, [](uint8_t d, uint8_t s) { return d | s; }); + } + void bit_and(void* d, size_t doff, const void* s, size_t soff, size_t n) { + bit_op(d, doff, s, soff, n, [](uint8_t d, uint8_t s) { return d & s; }); + } +} +constexpr uint8_t reverse8(uint8_t b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} +constexpr std::array make_reverse8_table() { + std::array arr = {}; + for (uint16_t i = 0; i < 256; ++i) arr[i] = reverse8(static_cast(i)); + return arr; +} +constexpr auto reserve8 = make_reverse8_table(); +#define BYTE(b) reserve8[0b##b] +#ifdef _USE_GTEST +#include +using namespace yC; +// 辅助函数:字节数组转十六进制字符串(方便调试) +std::string bytes_to_hex(const std::vector& v) { + std::ostringstream oss; + for (auto b : v) { + oss << std::hex << std::setw(2) << std::setfill('0') << (int)b; + } + return oss.str(); +} +// case 2:dest 偏移,src 对齐 +TEST(BitMoveTest, DestBitOffset) { + struct Data { + std::vector dest; + size_t dest_offset; + std::vector src; + size_t src_offset; + size_t num; + std::vector expected_move; + std::vector expected_xor; + std::vector expected_or; + std::vector expected_and; + std::string msg; + }; + std::vector list; + // 1. 目标偏移2位,拷贝6位 + // 00 00000000 + // dest 000000 + // src 111100 + // xor: 111100 + // or: 111100 + // and: 000000 + Data d1{ + {BYTE(00000000), BYTE(00000000)}, + 2, + {BYTE(11110000), BYTE(00001111)}, + 0, + 6, + {BYTE(00111100), BYTE(00000000)}, + // move + {BYTE(00111100), BYTE(00000000)}, + // xor (0x3C, 0x00) + {BYTE(00111100), BYTE(00000000)}, + // or (0x3C, 0x00) + {BYTE(00000000), BYTE(00000000)}, + // and (0x00, 0x00) + "目标偏移2位,拷贝6位" + }; + + char arr[100]; + size_t a = get_mem2hex_size(2, 0); + mem2hex_Ex2(arr, d1.expected_move.data(), 2, false, ""); + + + + EXPECT_STREQ(hex2bin(std::string(arr, a)).c_str(), "0011110000000000"); + + + // 2. 目标偏移1位,拷贝9位,跨越目标字节 + // 1 000001 + // dest 000000000 + // src 011111111 + // xor: 011111111 + // or: 011111111 + // and: 000000000 + list.push_back({ + {BYTE(10000000), BYTE(00000001)}, + 1, + {BYTE(01111111), BYTE(10000000)}, + 0, + 9, + {BYTE(10111111), BYTE(11000001)}, + // move + {BYTE(10111111), BYTE(11000001)}, + // xor (0xFE, 0x83) + {BYTE(10111111), BYTE(11000001)}, + // or (0xFE, 0x83) + {BYTE(10000000), BYTE(00000001)}, + // and (0x01, 0x03) + "目标偏移1位,拷贝9位,跨字节" + }); + // 3. 目标偏移4,源偏移3,拷贝8位 + // 1100 1010 + // dest 00111010 + // src 10000000 + // xor 10111010 + // or 10111010 + // and 00000000 + list.push_back({ + {BYTE(11000011), BYTE(10101010)}, + 4, + {BYTE(11110000), BYTE(00001111)}, + 3, + 8, + {BYTE(11001000), BYTE(00001010)}, + // move + {BYTE(11001011), BYTE(10101010)}, + // xor + {BYTE(11001011), BYTE(10101010)}, + // or + {BYTE(11000000), BYTE(00001010)}, + // and + "目标偏移4,源偏移3,拷贝8位" + }); + // 4. 全1拷贝部分,全0目标 + // 00000 0000 + // dest 0000000 + // src 1111111 + // xor 1111111 + // or 1111111 + // and 0000000 + list.push_back({ + {BYTE(00000000), BYTE(00000000)}, + 5, + {BYTE(11111111), BYTE(11111111)}, + 2, + 7, + {BYTE(00000111), BYTE(11110000)}, + // move + {BYTE(00000111), BYTE(11110000)}, + // xor + {BYTE(00000111), BYTE(11110000)}, + // or + {BYTE(00000000), BYTE(00000000)}, + // and + "全1源,拷贝7位到目标末尾" + }); + // 5. 源目标均有内容(交错) + // 110 10011 + // dest 01100001 + // src 10100101 + // xor 11000100 + // or 11100101 + // and 00100001 + list.push_back({ + {BYTE(11001100), BYTE(00110011)}, + 3, + {BYTE(10101010), BYTE(01010101)}, + 4, + 8, + {BYTE(11010100), BYTE(10110011)}, + // move + {BYTE(11011000), BYTE(10010011)}, + // xor + {BYTE(11011100), BYTE(10110011)}, + // or + {BYTE(11000100), BYTE(00110011)}, + // and + "目标有内容,源偏移4,拷贝8位" + }); + // 6. 源目标完全重叠 + // 1011 01 + // dest 1001110100 + // src 1101111010 + // move 1101111010 + // xor 0100001110 + // or 1101111110 + // and 1001110000 + list.push_back({ + {BYTE(10111001), BYTE(11010001)}, + 4, + {BYTE(10110111), BYTE(10100101)}, + 2, + 10, + {BYTE(10111101), BYTE(11101001)}, + // move + {BYTE(10110100), BYTE(00111001)}, + // xor + {BYTE(10111101), BYTE(11111001)}, + // or + {BYTE(10111001), BYTE(11000001)}, + // and + "目标/源重叠,偏移不同" + }); + for (auto& d : list) { + auto dest_move = d.dest; + auto dest_xor = d.dest; + auto dest_or = d.dest; + auto dest_and = d.dest; + bit_move(dest_move.data(), d.dest_offset, d.src.data(), d.src_offset, d.num); + bit_xor(dest_xor.data(), d.dest_offset, d.src.data(), d.src_offset, d.num); + bit_or(dest_or.data(), d.dest_offset, d.src.data(), d.src_offset, d.num); + bit_and(dest_and.data(), d.dest_offset, d.src.data(), d.src_offset, d.num); + EXPECT_EQ(dest_move, d.expected_move) << "move 结果: " << hex2bin(bytes_to_hex(dest_move)) << "\n期望: " << + hex2bin(bytes_to_hex(d.expected_move)) << "\n" + d.msg; + EXPECT_EQ(dest_xor, d.expected_xor) << "xor 结果: " << bytes_to_hex(dest_xor) << "\n期望: " << + bytes_to_hex(d.expected_xor) << "\n" + d.msg; + EXPECT_EQ(dest_or, d.expected_or) << "or 结果: " << bytes_to_hex(dest_or) << "\n期望: " << + bytes_to_hex(d.expected_or) << "\n" + d.msg; + EXPECT_EQ(dest_and, d.expected_and) << "and 结果: " << bytes_to_hex(dest_and) << "\n期望: " << + bytes_to_hex(d.expected_and) << "\n" + d.msg; + } + +} + + +TEST(Bit222, WriteAndReadFullByte223) { + + + + + std::pmr::string s("11101110"); + std::string s2("11101110"); + std::cout << bin2hex(s) << std::endl; + std::cout << bin2hex(s2) << std::endl; + std::cout << bin2hex("11101110") << std::endl; + + + std::cout << "111:" << hex2bin("01") << std::endl; + std::cout << "big_dian:" << bin4_to_hex(true, "0011") << std::endl; + std::cout << "small_dian:" << bin4_to_hex(false, "0011") << std::endl; +} + +TEST(BitOpsLE, WriteAndReadFullByte) { + uint8_t buf[8] = {0}; + set_bits(buf, 0, 8, 0xAB); // 小端模式:低位在低bit + EXPECT_EQ(buf[0], 0xAB); + EXPECT_EQ(get_bits(buf, 0, 8), 0xAB); +} +TEST(BitOpsLE, CrossByteWriteAndRead) { + uint8_t buf[8] = {0}; + set_bits(buf, 4, 12, 0xF0F); // 写12位,偏移4 + EXPECT_EQ(get_bits(buf, 4, 12), 0xF0F); +} +TEST(BitOpsLE, UnalignedBitWriteAndRead) { + uint8_t buf[8] = {0}; + set_bits(buf, 5, 10, 0x2AA); // 偏移5位,写10位 + EXPECT_EQ(get_bits(buf, 5, 10), 0x2AA); +} +TEST(BitOpsLE, SingleBitWriteAndRead) { + uint8_t buf[8] = {0}; + set_bits(buf, 10, 1, 1); + EXPECT_EQ(get_bits(buf, 10, 1), 1); + set_bits(buf, 10, 1, 0); + EXPECT_EQ(get_bits(buf, 10, 1), 0); +} +TEST(BitOpsLE, WriteAndReadFullUint64) { + uint8_t buf[8] = {0}; + set_bits(buf, 0, 64, 0x123456789ABCDEF0ULL); + EXPECT_EQ(get_bits(buf, 0, 64), 0x123456789ABCDEF0ULL); +} +TEST(BitOpsLE, OverwriteZeroBits) { + uint8_t buf[8]; + std::memset(buf, 0xFF, sizeof(buf)); + set_bits(buf, 16, 16, 0x0000); + EXPECT_EQ(get_bits(buf, 16, 16), 0x0000); + // 其它位应保持原值 + for (size_t i = 0; i < 2; ++i) + EXPECT_EQ(buf[i], 0xFF); + for (size_t i = 4; i < 8; ++i) + EXPECT_EQ(buf[i], 0xFF); +} +TEST(BitOpsLE, AllZeroAndAllOne) { + uint8_t buf[8] = {0}; + set_bits(buf, 0, 64, 0xFFFFFFFFFFFFFFFFULL); + EXPECT_EQ(get_bits(buf, 0, 64), 0xFFFFFFFFFFFFFFFFULL); + set_bits(buf, 0, 64, 0x0ULL); + EXPECT_EQ(get_bits(buf, 0, 64), 0x0ULL); +} +#else +#endif diff --git a/module/dll_source_lzy/ecap/lib/hulc/Bit.h b/module/dll_source_lzy/ecap/lib/hulc/Bit.h new file mode 100644 index 0000000..59e8c58 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/Bit.h @@ -0,0 +1,309 @@ +#ifndef BitSet_H +#define BitSet_H +#include +#ifdef __linux__ +#include +#endif +#include +#include +#include +#include + +namespace yC { + template + typename std::enable_if::value, T>::type + create_string(size_t size, std::pmr::memory_resource* resource = nullptr) { + std::pmr::memory_resource* r = resource ? resource : std::pmr::get_default_resource(); + std::pmr::string ret(r); + ret.resize(size); + return ret; // 返回 std::pmr::string + } + + template + typename std::enable_if::value, T>::type + create_string(size_t size, std::pmr::memory_resource* resource = nullptr) { + std::string ret; + ret.resize(size); + return ret; // 返回 std::string + } + + template + struct cacl_type { + using type = T; + size_t size(T& a) { + return a.size(); + } + static void* data(T& a) { + return (void*)a.data(); + } + static size_t size(const T& a) { + return a.size(); + } + static void* data(const T& a) { + return (void*)a.data(); + } + }; + template <> + struct cacl_type { + using type = std::string; + static size_t size(const char* a) {return strlen(a);} + static void* data(const char* a) {return (void*)a;} + }; + + template + struct cacl_type { + using type = std::string; + static size_t size(const char (&a)[N]) { return N - 1; } + static void* data(const char (&a)[N]) { return (void*)a; } + }; + + template + struct cacl_type { + using type = std::string; + static size_t size(const char (&a)[N]) { return N - 1; } + static void* data(const char (&a)[N]) { return (void*)a; } + }; +} + + + +#define Base template , \ +typename STR_Type = std::basic_string, allocator_type>, \ +typename ret_type = typename cacl_type::type \ +> +// #define Base template <\ +// typename STR_Type, \ +// typename ret_type = typename cacl_type::type \ +// > + + +namespace yC { + // 检查当前机器是否是大端字节序 + extern const bool is_big_endian; + void big_endian_2_platform(void* dest, const char* d, size_t size); + void little_endian_2_platform(void* dest, const char* d, size_t size); + + + + void big_endian_2_platform(const std::string& big_endian, char* dest); + void little_endian_2_platform(const std::string& little_endian, char* dest); + + + std::string big_endian_2_platform(const std::string& big_endian); + std::string little_endian_2_platform(const std::string& little_endian); + std::string platform_2_big_endian(void* memory, int size); + std::string platform_2_little_endian(void* memory, int size); + + + + + inline size_t get_xor_hex_size(size_t n1, size_t n2){return std::max(n1, n2);} + void xor_hex_Ex(void* dest, const char* s1, size_t n1, const char* s2, size_t n2, bool big_dian, bool is_upper); + Base + ret_type xorHex(const STR_Type& s1, const STR_Type& s2, std::pmr::memory_resource* resource = nullptr) { + auto n1 = cacl_type::size(s1); + auto n2 = cacl_type::size(s2); + auto size = get_xor_hex_size(n1, n2); + auto ret = create_string(size, resource); + xor_hex_Ex((void*)ret.data(), (const char*)cacl_type::data(s1), n1, (const char*)cacl_type::data(s2), n2, true, true); + return ret; + } + + + inline size_t get_xor_bin_size(size_t n1, size_t n2){return std::max(n1, n2);} + void xor_bin_Ex(void* dest, const char* s1, size_t n1, const char* s2, size_t n2, bool big_dian); + Base + ret_type xorBin(const STR_Type& s1, const STR_Type& s2, std::pmr::memory_resource* resource = nullptr) { + auto n1 = cacl_type::size(s1); + auto n2 = cacl_type::size(s2); + auto size = get_xor_bin_size(n1, n2); + auto ret = create_string(size, resource); + xor_bin_Ex((void*)ret.data(), (const char*)cacl_type::data(s1), n1, (const char*)cacl_type::data(s2), n2, true); + return ret; + } + + + + + inline size_t get_bin2hex_size(size_t num) {return (num + 3) / 4;} + void bin2hex_Ex(void* dest, const char* data, size_t size, bool big_dian, bool isupper); + Base + ret_type bin2hex(const STR_Type& mem, std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_bin2hex_size(size), resource); + bin2hex_Ex((void*)ret.data(), (const char*)cacl_type::data(mem), size, true, true); + return ret; + } + + + + inline size_t get_hex2bin_size(size_t num) {return num * 4;} + void hex2bin_Ex(void* dest, const char* data, size_t size, bool big_dian); + Base + ret_type hex2bin(const STR_Type& mem, std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_hex2bin_size(size), resource); + hex2bin_Ex((void*)ret.data(), (const char*)cacl_type::data(mem), size, true); + return ret; + } + + inline size_t get_bin2mem_size(size_t num) {return num/8;}; + void bin2mem(void* dest, const char* data, size_t size); + Base + STR_Type bin2mem(const STR_Type& mem, std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_bin2mem_size(size), resource); + bin2mem((void*)ret.data(), (const char*)cacl_type::data(mem), size); + return ret; + } + + // inline ret_str bin2mem(param_str mem, std::pmr::memory_resource* resource = nullptr) { + // auto size = mem.size(); + // auto ret = ret_str(); + // ret.resize(get_bin2mem_size(size)); + // bin2mem((void*)ret.data(), (const char*)mem.data(), size); + // return ret; + // } + + + inline size_t get_hex2mem_size(size_t hex_hum) {return hex_hum/2;}; + void hex2mem_Ex(void* dest, const char* data, size_t size); + Base + STR_Type hex2mem(const STR_Type& mem, std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_hex2mem_size(size), resource); + hex2mem_Ex((void*)ret.data(), (const char*)cacl_type::data(mem), size); + return ret; + } + + // inline ret_str hex2mem(param_str hex, std::pmr::memory_resource* resource = nullptr) { + // auto size = hex.size(); + // auto ret = ret_str(); + // ret.resize(get_hex2mem_size(size)); + // hex2mem_Ex((void*)ret.data(), (const char*)hex.data(), size); + // return ret; + // } + + + inline size_t get_mem2hex_size(size_t num, size_t middle_len) { return num * 2 + (num - 1) * middle_len;} + void mem2hex_Ex2(void* dest, void* mem, size_t num, bool uppercase, const char* middle, size_t middle_len = std::numeric_limits::max()); + Base + STR_Type mem2hex(const STR_Type& mem, bool uppercase = true, const std::string& middle = "", std::pmr::memory_resource* resource = nullptr) { + auto size = cacl_type::size(mem); + auto ret = create_string(get_mem2hex_size(size, middle.size()), resource); + mem2hex_Ex2(ret.data(), cacl_type::data(mem), size, uppercase, middle.data(), middle.size()); + return ret; + } + + // inline ret_str mem2hex(param_str& mem, bool uppercase = true, const std::string& middle = "", std::pmr::memory_resource* resource = nullptr) { + // auto size = get_mem2hex_size(mem.size(), middle.size()); + // auto ret = ret_str(); + // mem2hex_Ex2(ret.data(), (void*)mem.data(), size, uppercase, middle.data(), middle.size()); + // return ret; + // } + + + template + void get_bin(std::string& msg, int& n) { + n = std::bitset(msg.substr(start, len)).to_ullong(); + } + template + void get_bin(std::string& msg, Enum& n) { + n = static_cast(std::bitset(msg.substr(start, len)).to_ullong()); + } + template + Enum get_bin(std::string& msg) { + return static_cast(std::bitset(msg.substr(start, len)).to_ullong()); + } + template + int get_bin(const std::string& msg) { + return std::bitset(msg.substr(start, len)).to_ullong(); + } + template + void set_bin(std::string& msg, unsigned long long n) { + msg.replace(start, len, std::bitset(n).to_string()); + } + template + void set_bin(std::string& msg, const std::string& value) { + if (value.size() != len) { + std::cout << "len == " << len << " bits.size() == " << value.size() << std::endl; + throw std::out_of_range("Start position and length exceed bit string size"); + } + msg.replace(start, len, value); + } + template + std::string to_bin(unsigned long long n) { + return std::bitset(n).to_string(); + } + template + std::string to_bin(double n) { + return std::bitset(static_cast(n)).to_string(); + } + template + T bin2(const std::string& binstr) { + return std::stoull(binstr, nullptr, 2); + } + template + unsigned long long hex2(const std::string& hexstr) { + return std::stoull(hexstr, nullptr, 16); + } + template <> + inline int bin2(const std::string& binstr) { + int bit_limit = sizeof(int) * 8; + if (binstr.size() > bit_limit) { + std::cout << "Bitset.h inline int bin2 error! Binary string exceeds int bit limit" << std::endl; + } + return std::stoi(binstr, nullptr, 2); + } + // 设置dest的从off位起n位,写入value的低n位 + template + void set_bits(void* dest, size_t off, size_t n, T value) { + auto d = static_cast(dest); + size_t bit_pos = off; + size_t val_pos = 0; + while (n > 0) { + size_t byte_idx = bit_pos / 8; + size_t bit_in_byte = bit_pos % 8; + size_t bits_in_this_byte = std::min(n, 8 - bit_in_byte); + // 为当前字节构建掩码 + uint8_t mask = ((1u << bits_in_this_byte) - 1) << bit_in_byte; + // 取value对应的低bits_in_this_byte位 + uint8_t v = (value >> val_pos) & ((1u << bits_in_this_byte) - 1); + // 清除目标字节对应位置后设置 + d[byte_idx] = (d[byte_idx] & ~mask) | ((v << bit_in_byte) & mask); + bit_pos += bits_in_this_byte; + val_pos += bits_in_this_byte; + n -= bits_in_this_byte; + } + } + + + + // 大批量位处理性能不好 但是通常也没有这样的场景 + void bit_move(void* d, size_t doff, const void* s, size_t soff, size_t n); + void bit_xor(void* d, size_t doff, const void* s, size_t soff, size_t n); + void bit_or(void* d, size_t doff, const void* s, size_t soff, size_t n); + void bit_and(void* d, size_t doff, const void* s, size_t soff, size_t n); + template + T get_bits(void* src, size_t off, size_t n) { + auto s = static_cast(src); + size_t bit_pos = off; + size_t val_pos = 0; + T result = 0; + while (n > 0) { + size_t byte_idx = bit_pos / 8; + size_t bit_in_byte = bit_pos % 8; + size_t bits_in_this_byte = std::min(n, 8 - bit_in_byte); + uint8_t mask = ((1u << bits_in_this_byte) - 1) << bit_in_byte; + uint8_t bits = (s[byte_idx] & mask) >> bit_in_byte; + result |= (T(bits) << val_pos); + bit_pos += bits_in_this_byte; + val_pos += bits_in_this_byte; + n -= bits_in_this_byte; + } + return result; + } +} + +#undef Base +#endif diff --git a/module/dll_source_lzy/ecap/lib/hulc/DataOutputFormats.cpp b/module/dll_source_lzy/ecap/lib/hulc/DataOutputFormats.cpp new file mode 100644 index 0000000..33c3fa0 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/DataOutputFormats.cpp @@ -0,0 +1,106 @@ +#include "DataOutputFormats.h" + +#include +#include + +using namespace yC; + +MLAT_timestamp::MLAT_timestamp() { + std::chrono::time_point t = std::chrono::high_resolution_clock::now(); + // 获取当前的系统时间(秒) + std::chrono::duration duration_since_epoch = t.time_since_epoch(); + // 获取秒数和纳秒数 + auto seconds = std::chrono::duration_cast(duration_since_epoch); + auto nanoseconds = std::chrono::duration_cast(duration_since_epoch); + // 获取当天的秒数和纳秒数 + this->daysec = seconds.count() % 86400; // 86400 = 24 * 60 * 60 (一天的秒数) + this->nanosec = nanoseconds.count() % 86400000000000; // 1秒 = 10^9纳秒 + memory = bin2mem(to_bin_string()); + parse_hhmmss(); +} + + +MLAT_timestamp::MLAT_timestamp(const std::string& memory_6) : memory(memory_6) { + assert(memory_6.size() == 6); + auto msg_bin = hex2bin(mem2hex(memory)); + daysec = std::bitset<18>(msg_bin.substr(0, 18)).to_ulong(); + nanosec = std::bitset<30>(msg_bin.substr(18, 30)).to_ulong(); + parse_hhmmss(); + + //assert(memory_6 == bin2mem(to_bin_string()), "MLAT_timestamp,内存转换错误!"); +} + +MLAT_timestamp::MLAT_timestamp(std::uint32_t daysec, std::uint32_t nanosec) : daysec(daysec), nanosec(nanosec) { + parse_hhmmss(); +} + + +std::string MLAT_timestamp::to_bin_string() const { + std::bitset<18> daysec_bits = daysec; + std::bitset<30> nanosec_bits = nanosec; + return daysec_bits.to_string() + nanosec_bits.to_string(); +} + +// std::string MLAT_timestamp::to_memory() { +// return bin2memory(to_bin_string()); +// } + +std::string MLAT_timestamp::to_memory() const { + return memory; +} + +void MLAT_timestamp::parse_hhmmss() { + hh = (daysec/3600) % 24; + mm = (daysec/60) % 60; + ss = daysec % 60; +} +std::string packet_to_escape_format(std::string t) { + std::string result = "\x1a"; + for (size_t i = 1; i < t.size(); ++i) { + result.push_back(t[i]); // 插入当前字符 + if (t[i] == '\x1a') { + result.push_back('\x1a'); // 如果是 0x1a,插入另一个 0x1a + } + } + return result; +} +void Binary_Format_handle_buffer(std::string& buffer, const std::string& data, const std::function& callback) { + size_t i = 0; + while (i < data.size()) { + if (data[i] == 0x1A) { + // 如果遇到起始标记 0x1A + if (i + 1 < data.size() && data[i + 1] == 0x1A) { + // 如果是转义的 0x1A 0x1A,继续处理 + buffer.push_back(0x1A); // 添加一个正常的 0x1A 字节 + i += 2; // 跳过这两个字节 + } else { + // 检测到起始标记 0x1A,认为是新消息的开始 + if (!buffer.empty()) { + // 如果当前消息 buffer 中已有数据,调用回调函数处理当前消息 + callback(buffer); + buffer.clear(); // 清空当前消息的缓存 + } + buffer.push_back(0x1A); + // 跳过当前的 SOP 标记 (0x1A) + i++; + } + } else + { + // 如果不是 0x1A,直接将数据添加到 buffer 中 + buffer.push_back(data[i]); + i++; + } + } +} + + + +// 9 + 2 +// 9 + 7 +// 9 + 14 +std::string mode_s_msg_packet_to_server_packet(std::uint32_t id, std::string packet) { + std::string id_code = yC::platform_2_big_endian((char*)&id, sizeof(id)); + //assert(id_code.size() == 4, "id.size != 4"); + packet.insert(1, id_code.c_str(), 4); + return packet; +} \ No newline at end of file diff --git a/module/dll_source_lzy/ecap/lib/hulc/DataOutputFormats.h b/module/dll_source_lzy/ecap/lib/hulc/DataOutputFormats.h new file mode 100644 index 0000000..aab359e --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/DataOutputFormats.h @@ -0,0 +1,531 @@ +#ifndef DATAOUTPUTFORMATS_H +#define DATAOUTPUTFORMATS_H +#include "base_format.h" +using namespace yC; + +// AVR format +// *02E99619FACDAE; +// *8D3C5EE69901BD9540078D37335F; +// *7700; +inline std::string create_AVR_format(const std::string& msg_hex) { + return "*" + msg_hex + ";"; +} + +// @016CE3671C7423FFE7AB7BFCAB; +// @016CE3671AA8A800199A8BB80030A8000628F400; +// @016CE3671C747700; + +// @5500068BA46CB402c18d34fc8000; +inline std::string create_MLAT_AVR_format(const std::string& msg_hex, MLAT_timestamp* timestamp) { + //std::string signal_level_str = bin2hex(std::bitset<8>(signal_level).to_string()); + //std::cout << "signal:" << signal_level_str << std::endl; + return "@" + bin2hex(timestamp->to_bin_string()) + msg_hex + ";"; +} + + +// Binary Format +// "1" : 6 byte MLAT timestamp, 1 byte signal level, 2 byte Mode-AC +// "2" : 6 byte MLAT timestamp, 1 byte signal level, 7 byte Mode-S short frame +// "3" : 6 byte MLAT timestamp, 1 byte signal level, 14 byte Mode-S long frame +// "4" : 6 byte MLAT timestamp, 1 byte unused, DIP switch configuration settings, time stamp error ticks as int8_t (1 tick is 15ns) (message "4" not on Mode-S Beast classic) +// : true 0x1a +// is 0x1a, and "1", "2" and "3" are 0x31, 0x32 and 0x33 +inline std::string create_Binary_Format_memory(const std::string& msg_hex, char signal_level, MLAT_timestamp* timestamp) { + std::string type; + switch (msg_hex.size()) { + case 4: type = '1'; + break; + case 14: type = '2'; + break; + case 28: type = '3'; + break; + default: std::cerr << msg_hex << "create_Binary_Format error is not a valid binary format." << std::endl; + throw std::runtime_error("create_Binary_Format error!"); + } + + + std::string t = timestamp->to_memory() + signal_level + yC::hex2mem(msg_hex); + + // std::cout << "msg_hex.size():" << msg_hex.size() << std::endl; + // std::cout << "hex2memory(msg_hex):" << hex2memory(msg_hex).size() << std::endl; + // std::cout << t.size() << std::endl; + std::string result; + + // 遍历原始字符串 + for (size_t i = 0; i < t.size(); ++i) { + result.push_back(t[i]); // 插入当前字符 + if (t[i] == '\x1a') { + result.push_back('\x1a'); // 如果是 0x1a,插入另一个 0x1a + } + } + + return char(0x1a) + type + result; +} + +// 把要传递的信息转化成转义字符 二进制==>转义二进制 +std::string packet_to_escape_format(std::string t); + +inline std::string unescape_packet(std::string t) { + std::string result; + for (size_t i = 0; i < t.size(); ++i) { + if (t[i] == '\x1a' && i + 1 < t.size() && t[i + 1] == '\x1a') { + result.push_back('\x1a'); // 如果是连续的两个 \x1a,只添加一个 + ++i; // 跳过下一个 \x1a + } else { + result.push_back(t[i]); // 否则,直接添加当前字符 + } + } + return result; +} + + +// 用于dip_setting +// FPGA switch settings +// Bit 0: Data output mode(port 10002 only) Binary AVR hexdump +// Bit 1: Raw data frame types DF11, DF17 and DF18 only no pre-filtering +// Bit 2: Timestamps within AVR format enabled disabled +// Bit 3: not used +// Bit 4: Timestamp mode GPS legacy 12MHz +// Bit 5: FPGA/USB handshake disabled enabled +// Bit 6: 1 bit forward error correction (FEC) disabled enabled +// Bit 7: Mode-A/C decoding enabled disabled +// GPS status information +// Bit 0: Antenna status good fail +// Bit 1: Tracking at least 1 satellite true false +// Bit 2: Tracking at least 3 satellites true false +// Bit 3: Time value base UTC time GPS time (8sec offset) +// Bit 4: unused +// Bit 5: unused +// Bit 6: unused +// Bit 7: Daysec value range 0 ... 86399 1 ... 86400 +inline std::string create_Binary_Format_4(MLAT_timestamp timestamp, char signal_level, char dip_setting, char time_stamp_error_ticks) { + return char(0x1a) + "4" + timestamp.to_bin_string() + char(0xaa) + dip_setting + time_stamp_error_ticks; +} + +//相对时间戳(无 GPS):在没有连接 GPS 接收器的情况下,时间戳是一个 48 位无符号数字, +//计数 12 MHz 周期。时间戳大约每 271 天换行一次。 +struct TCXO_Clock { + unsigned long long times; + + explicit TCXO_Clock(const std::string& hex) { + times = std::bitset<48>(hex2bin(hex)).to_ullong(); + } + + [[nodiscard]] std::string to_string() const { + // 每个周期的时间是 1 / 12,000,000 秒 + double seconds = static_cast(times) / 12000000.0; + // 计算小时、分钟、秒 + int hours = static_cast(seconds / 3600); + seconds -= hours * 3600; + int minutes = static_cast(seconds / 60); + seconds -= minutes * 60; + int secs = static_cast(seconds); + // 格式化为时分秒字符串 + std::ostringstream oss; + oss.fill('0'); + oss << hours << ":"; + oss.width(2); + oss << minutes << ":"; + oss.width(2); + oss << secs; + return oss.str(); + } +}; + +/* +Periodic status message which is emitted every second +0x1A 0x48 0x01 0x18 SerNum Flags I.U. xTime Lat Lon Alt Sat HDOP +Fields: + SerNum 32-bit(1) Device Serial Number + Flags 16-bit(1) Status Flags* + I.U. 16-bit Internal Use + xTime 32-bit(1) Unix-Timestamp (Seconds since midnight 1970-01-01, UTC) + Lat 32-bit(1) Latitude as 32-bit BAM 2, thus 232 ≡ 360° + Lon 32-bit(1) Latitude as 32-bit BAM 2 + Alt 16-bit(1) Signed altitude meters + Sat 8-bit Number of satellites used in fix + HDOP 8-bit HDOP * 10, thus a value of 12 is HDOP 1.2 +1) MSB-first +2) https://en.wikipedia.org/wiki/Binary_scaling#Binary_angles + Status Flags: + 1xxx xxxx xxxx xxxx - GPS device detected -> Using absolute timestamp + x1xx xxxx xxxx xxxx - GPS valid (GGA, RMC present) + xx1x xxxx xxxx xxxx - GPS currently has a valid fix + xxx1 xxxx xxxx xxxx - High accuracy absolute time is available via PPS + xxxx xxxx 1xxx xxxx - TX-Queue overflow since start-up (normal if HW-handshaking is used) + xxxx xxxx x1xx xxxx - TX-Queue overflow during last second (probably due to HW-handshaking) + xxxx xxxx xx1x xxxx - Excessive NMEA found (other than GGA/RMC) +*/ +// +// All HULC messages (Type 0x48) have the following structure: +// 0x1A 0x48 ID LEN +// Fields: +// ID: HULC Message ID +// LEN: Number of bytes following +struct HULC_Message { + std::uint8_t head; + std::uint8_t id; + std::uint8_t len; // 指示下面的长度 +}; + + + + +struct HULC_Status_Message : HULC_Message { + std::uint32_t SerNum{}; + std::uint16_t Flags{}; // MSB大端字节序 + std::uint16_t I_U_{}; // Internal Use + std::uint32_t xTime{}; // Unix-Timestamp (Seconds since midnight 1970-01-01, UTC) + std::int32_t Lat{}; + std::int32_t Lon{}; + std::int16_t Alt{}; + std::uint8_t Sat{}; // Sat字段记录了定位过程中使用的卫星数量 + std::uint8_t HDOP{}; + + [[nodiscard]] double get_HDOP() const { + // HDOP(Horizontal Dilution of Precision) 描述的是 水平精度的衰减,即 GPS 系统的水平定位精度与卫星的几何布局有关。HDOP 数值越低,说明卫星的几何布局越好,定位精度越高。 + // 较低的 HDOP 值(例如 1-2)表示卫星信号较强,且卫星的分布较为理想,因此定位精度较高。 + // 较高的 HDOP 值(例如 5 或更高)表示卫星信号较弱,或卫星的分布较差,可能导致较差的定位精度。 + return double(HDOP) / 10.0; + } + + void set_latitude(double latitude) { + // 根据经纬度设置 Lat 值 + Lat = static_cast((latitude / 360.0) * pow(2, 32)); + } + + void set_longitude(double longitude) { + // 根据经纬度设置 Lon 值 + Lon = static_cast((longitude / 360.0) * pow(2, 32)); + } + + [[nodiscard]] double get_latitude() const { + return (static_cast(Lat) / pow(2, 32)) * 360.0; + } + + [[nodiscard]] double get_longitude() const { + return (static_cast(Lon) / pow(2, 32)) * 360.0; + } + + [[nodiscard]] std::string get_latitude_d_m_s() const{ + return decimalToDMS(get_latitude()); + } + + [[nodiscard]] std::string get_longitude_d_m_s() const{ + return decimalToDMS(get_longitude()); + } + + [[nodiscard]] std::string get_timestamp_string() const{ + return convert_timestamp_to_string(xTime); + } + + static std::string convert_timestamp_to_string(std::uint32_t time32) { + std::time_t time = time32; + std::stringstream ss; + ss << std::put_time(std::gmtime(&time), "%Y-%m-%d %H:%M:%S"); // 使用 std::put_time 格式化日期 + return ss.str( + + ); + // 返回格式化后的字符串 + // std::time_t time = time32; + // std::stringstream ss; + // // 将时间戳转换为本地时间 + // ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S"); // 使用 std::localtime 格式化日期 + // return ss.str(); // 返回格式化后的字符串 + // // Unix Timestamp + // std::time_t timestamp = xTime; + // + // std::tm* ptm = std::gmtime(×tamp); + // + // return std::asctime(ptm); + } + + + // 转化经纬度变成 度 分 秒 + static std::string decimalToDMS(double decimalDegree) { + int degrees = static_cast(decimalDegree); + double minutesDouble = (decimalDegree - degrees) * 60; + int minutes = static_cast(minutesDouble); + int seconds = static_cast((minutesDouble - minutes) * 60.0); + return "(" + std::to_string(degrees) + "°" + std::to_string(minutes) + "'" + std::to_string(seconds) + "\")"; + } + /* + Status Flags: + 1xxx xxxx xxxx xxxx - GPS device detected -> Using absolute timestamp + x1xx xxxx xxxx xxxx - GPS valid (GGA, RMC present) + xx1x xxxx xxxx xxxx - GPS currently has a valid fix + xxx1 xxxx xxxx xxxx - High accuracy absolute time is available via PPS + xxxx xxxx 1xxx xxxx - TX-Queue overflow since start-up (normal if HW-handshaking is used) + xxxx xxxx x1xx xxxx - TX-Queue overflow during last second (probably due to HW-handshaking) + xxxx xxxx xx1x xxxx - Excessive NMEA found (other than GGA/RMC) + */ + [[nodiscard]] bool GPS_device_detected() const { + return Flags & (1 << 15); // 检查第 15 位,GPS 设备是否检测到 + } + + [[nodiscard]] bool GPS_valid() const { + return Flags & (1 << 14); // 检查第 14 位,GPS 数据是否有效 + } + + [[nodiscard]] bool GPS_has_valid_fix() const { + return Flags & (1 << 13); // 检查第 13 位,GPS 是否有有效定位 + } + + [[nodiscard]] bool GPS_high_accuracy_time() const { + return Flags & (1 << 12); // 检查第 12 位,是否通过 PPS 提供高精度绝对时间 + } + + [[nodiscard]] bool TX_queue_overflow_since_startup() const { + return Flags & (1 << 7); // 检查第 7 位,自启动以来是否发生 TX 队列溢出 + } + + [[nodiscard]] bool TX_queue_overflow_last_second() const { + return Flags & (1 << 6); // 检查第 6 位,过去一秒内是否发生 TX 队列溢出 + } + + [[nodiscard]] bool excessive_NMEA_found() const { + return Flags & (1 << 5); // 检查第 5 位,是否发现过多的 NMEA 数据(非 GGA/RMC) + } + + [[nodiscard]] Json toJson() const { + Json ret = Json::object(); + ret.append({"序列号", SerNum}); + ret.append({"状态标志", Flags}); + ret.append({"内部使用", I_U_}); + ret.append({"时间戳", get_timestamp_string()}); + ret.append({"纬度", std::to_string(get_latitude()) + " " + get_latitude_d_m_s()}); + ret.append({"经度", std::to_string(get_longitude()) + " " + get_longitude_d_m_s()}); + ret.append({"高度", Alt}); + ret.append({"卫星数量", Sat}); + ret.append({"HDOP", get_HDOP()}); + ret.append({"GPS设备检测到", GPS_device_detected()}); + ret.append({"GPS有效", GPS_valid()}); + ret.append({"GPS有效定位", GPS_has_valid_fix()}); + ret.append({"GPS高精度时间", GPS_high_accuracy_time()}); + ret.append({"启动以来TX队列溢出", TX_queue_overflow_since_startup()}); + ret.append({"过去一秒TX队列溢出", TX_queue_overflow_last_second()}); + ret.append({"发现过多NMEA数据", excessive_NMEA_found()}); + return ret; + } + + + static double decodeLatitudeFromBAM(const std::string& bigdian4) { + std::uint32_t value; + yC::big_endian_2_platform(bigdian4, (char*)&value); + // BAM 格式解码公式 + // return (static_cast(value) / pow(2, 32)) * 360.0; + return (static_cast(value) / pow(2, 32)) * 360.0; + } + std::string toBinary() { + std::string binary = {0x1a, 0x48, 0x01, 0x18}; + binary.append(yC::platform_2_big_endian(&SerNum, 4)); + binary.append(yC::platform_2_big_endian(&Flags, 2)); + binary.append(std::string((char*)&I_U_, 2)); // 内部使用没说什么序 默认大端序 + binary.append(yC::platform_2_big_endian(&xTime, 4)); + binary.append(yC::platform_2_big_endian(&Lat, 4)); + binary.append(yC::platform_2_big_endian(&Lon, 4)); + binary.append(yC::platform_2_big_endian(&Alt, 2)); + // 没说什么序 一字节 大端小端 等同 + binary.append({(char)Sat}); + binary.append({(char)HDOP}); + return binary; + } +}; + + + +static void hulc_big_endian_set(const std::string& msg, void* field, int start, int len) { + auto str = msg.substr(start, len); + yC::big_endian_2_platform(str, (char*)field); +} + +static void hulc_set2(const std::string& msg, void* field, int start, int len) { + auto it = msg.substr(start, len); + std::memcpy(field, it.data(), len); +} +// msg 是文本二进制字符串 转义后的 1a34 +static HULC_Status_Message create_HULC_Status_Message(const std::string& msg) { + HULC_Status_Message ret{}; + hulc_big_endian_set(msg, &ret.SerNum, 4, 4); + hulc_big_endian_set(msg, &ret.Flags, 8, 2); + hulc_set2(msg, &ret.I_U_, 10, 2); + hulc_big_endian_set(msg, &ret.xTime, 12, 4); + hulc_big_endian_set(msg, &ret.Lat, 16, 4); + hulc_big_endian_set(msg, &ret.Lon, 20, 4); + hulc_big_endian_set(msg, &ret.Alt, 24, 2); + hulc_set2(msg, &ret.Sat, 26, 1); + hulc_set2(msg, &ret.HDOP, 27, 1); + return ret; +} + + +struct HULC_Reply_to_command : HULC_Message { + std::uint8_t CMD; + std::uint8_t POO_14; +}; + +// 0x1A 0x48 0x01 0x18 SerNum Flags I.U. xTime Lat Lon Alt Sat HDOP +inline std::uint8_t get_id(const std::string& msg) { + return msg[2]; +} + +inline std::uint8_t get_len(const std::string& msg) { + return msg[3]; +} + +// Type Payload Size Description +// 0x31 4 bytes Mode-A/C raw data +// 0x32 7 bytes Mode-S Short Squitter raw data +// 0x33 14 bytes Mode-S Extended Squitter raw data +// 0x48 variable HULC Message, see below for details +// HULC Protocol uses the byte 0x1A as start-of-packet (SOP) marker. In order to avoid misinterpretation +// of normal data bytes with value 0x1A as SOP, each occurrence of data byte 0x1A is doubled in the +// data stream during transmission ('escaping'). It is thus necessary during reception to detect every +// 0x1A not followed by another 0x1A as an SOP marker and to reduce every occurrence of a double +// 0x1A to a single 0x1A data byte ('un-escaping'). +// Example of a 4-byte long packet including SOP marker and 0x1A data bytes: +// Before Escaping: 1A 32 27 1A E8 57 F0 1A 6C +// During Transmission: 1A 32 27 1A 1A E8 57 F0 1A 1A 6C +// After Un-Escaping: 1A 32 27 1A E8 57 F0 1A 6C +void Binary_Format_handle_buffer(std::string& buffer, const std::string& data, const std::function& callback); +/* Command Message (Host ==> Device) +The command-message has a simple ASCII based structure. Each message starts with a ‘#’ (0x23) and +ends with ‘’ (0x0D, 0x0A). In-between are one to 16 bytes in 2-digit hexadecimal representation +using upper-case letters, separated by ‘-’ (0x2D). +For example: + ‘#00’ or hexadecimal 0x23 0x30 0x30 0x0D 0x0A + ‘#43-02’ or hexadecimal 0x23 0x34 0x33 0x2D 0x30 0x32 0x0D 0x0A + The first byte is mandatory and holds the command while the remaining bytes are parameters. + Available commands depend on the selected protocol and are discussed in the respective protocol + section. +*/ + +std::string mode_s_msg_packet_to_server_packet(std::uint32_t id, std::string packet); + + + +// 1a34 radarcape 状态帧消息 +// https://wiki.jetvision.de/wiki/Radarcape:Version_History#Status_Frame_0x34_contents_were_extended_with_GPS_and_UTC_information +// 状态帧0x34内容已扩展为 GPS 和 UTC 信息 +// 当选择 GPS 时间戳时,状态帧由 1PPS 脉冲触发,在传统 12MHz 时间戳的情况下随机触发。 +// 状态帧包含有效的时间戳 +// 信号电平值为零/保留 + + +#define SETGET(name, pos) void set_##name(bool value) { set(pos, value); } \ +bool get_##name() { return get(pos); } + +#define SETGET2(Type, name, pos) void set_##name(Type value) { set(pos, (uint8_t)value); } \ +Type get_##name() { return (Type)get(pos); } + +struct BYTE_Settings { +protected: + uint8_t settings{}; // 8 位的字段,存储所有设置 + void set(uint8_t pos, bool value) { + if (value) { + // 设置指定位置的位为 1 + settings |= (1 << pos); + } else { + // 设置指定位置的位为 0 + settings &= ~(1 << pos); + } + } + [[nodiscard]] bool get(uint8_t pos) const { + // 获取指定位置的位值 + return (settings >> pos) & 0x01; + } +}; +struct FPGA_Setting : BYTE_Settings { + enum class Data_Output_Mode { AVR_hexdump, Binary }; + enum class Raw_Data_Frame_Type { No_Pre_Filtering, DF11_17_18_only }; + enum class Timestamp_Mode { Legacy_12MHz, GPS }; + SETGET2(Data_Output_Mode, data_output_mode, 0) + SETGET2(Raw_Data_Frame_Type, raw_data_frame_type, 1) + SETGET(timestamps_within_AVR_format, 2) + SETGET2(Timestamp_Mode, timestamp_mode, 4) + SETGET(serial_flow_control, 5) + SETGET(FEC, 6) // 1位转发错误校正 + SETGET(mode_ac_decoding , 7) + Json toJson() { + Json ret = Json::object(); + ret.append({"Data_Output_Mode", to_string(get_data_output_mode())}); + ret.append({"Raw_Data_Frame_Type", to_string(get_raw_data_frame_type())}); + ret.append({"timestamps_within_AVR_format", get_timestamps_within_AVR_format()}); + ret.append({"timestamps_mode", to_string(get_timestamp_mode())}); + ret.append({"serial_flow_control", get_serial_flow_control()}); + ret.append({"1位转发错误校正", get_FEC()}); + ret.append({"mode_ac_decoding", get_mode_ac_decoding()}); + return ret; + } + +}; + +struct GPS_Setting : BYTE_Settings { + enum class Time_Value_Base { UTC, GPS }; + enum class Daysec_Range { R_0_86399, R_1_86400 }; + SETGET(antenna_status, 0) // 位 0: 天线状态 + SETGET(tracking_at_least_1_satellite, 1) // 位 1: 至少跟踪 1 颗卫星 + SETGET(tracking_at_least_3_satellites, 2) // 位 2: 至少跟踪 3 颗卫星 + SETGET2(Time_Value_Base, time_value_base, 3) // 位 3: 时间值基数 (UTC/GPS) + SETGET(unused4, 4) // 位 4: 保留 + SETGET(unused5, 5) // 位 5: 保留 + SETGET(unused6, 6) // 位 6: 保留 + SETGET2(Daysec_Range, daysec_range, 7) // 位 7: Daysec 值范围 + Json toJson() { + Json ret = Json::object(); // 创建 JSON 对象 + ret.append({"antenna_status", get_antenna_status()}); + ret.append({"至少跟踪 1 颗卫星", get_tracking_at_least_1_satellite()}); + ret.append({"至少跟踪 3 颗卫星", get_tracking_at_least_3_satellites()}); + ret.append({"timestamps_mode", to_string(get_time_value_base())}); + ret.append({"daysec_range", to_string(get_daysec_range())}); + return ret; + } +}; + +struct Radarcape_STATUS_Message { + FPGA_Setting fpga_settings; + std::uint8_t pps_nanosecond_offset{}; // 包含 1PPS 事件时纳秒计时器的偏移量,单位为 15.625ns。正常值范围为 -3 ... +3。 + GPS_Setting gps_settings; + void set_pps_nanosecond(double value) { + if (value < 0.0 || value > 4000) { + std::cerr << "Invalid value. Must be in range [0.0, 4000.0]." << std::endl; + return; + } + pps_nanosecond_offset = static_cast((value)/15.625); + } + [[nodiscard]] double get_pps_nanosecond() const { + return (double)pps_nanosecond_offset * 15.625; + } + std::string toBinary() { + std::string binary = {0x1a, 0x34}; + binary.append(reinterpret_cast(&fpga_settings), 1); + binary.append(reinterpret_cast(&pps_nanosecond_offset), 1); + binary.append(reinterpret_cast(&gps_settings), 1); + return binary; + } + + Json toJson() { + Json ret = Json::object(); + ret.append({"pps_nanosecond_offset", get_pps_nanosecond()}); + ret.append({"fpga_settings", fpga_settings.toJson()}); + ret.append({"gps_settings", gps_settings.toJson()}); + return ret; + } + +}; + +// 消息5字节 +// +static Radarcape_STATUS_Message create_Radarcape_STATUS_Message(const std::string& msg_5) { + std::string msg = yC::big_endian_2_platform(msg_5.substr(2)); + Radarcape_STATUS_Message ret{}; + std::memcpy(&ret, (void*)(msg.data()), 3); + return ret; +} + + + +#undef SETGET +#undef SETGET2 + +#endif diff --git a/module/dll_source_lzy/ecap/lib/hulc/base_format.h b/module/dll_source_lzy/ecap/lib/hulc/base_format.h new file mode 100644 index 0000000..6708353 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/base_format.h @@ -0,0 +1,74 @@ + +#ifndef BASE_FORMAT_H +#define BASE_FORMAT_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Bit.h" +#include "Json.h" +#include "magic_enum/export.h" + + +// 1纳秒是 10 -9次方秒 +struct MLAT_timestamp { + explicit MLAT_timestamp(); + explicit MLAT_timestamp(const std::string& memory_6); + MLAT_timestamp(std::uint32_t daysec, std::uint32_t nanosec); + std::uint32_t daysec{}; // 当天的秒数 高18位 + std::uint32_t nanosec{}; // 当天的纳秒数 低30位 + std::string to_bin_string() const; + std::string to_memory() const; + unsigned long hh{}; + unsigned long mm{}; + unsigned long ss{}; + std::string memory{}; + bool operator<(const MLAT_timestamp& other) const { + // 比较 daysec(高18位) + if (daysec != other.daysec) { + return daysec < other.daysec; + } + // 如果 daysec 相等,比较 nanosec(低30位) + return nanosec < other.nanosec; + } + + + [[nodiscard]] Json toJson() const { + Json ret = Json::object(); + ret.append({"秒", daysec}); + ret.append({"纳秒", nanosec}); + ret.append({"时间", std::to_string(hh) + ":" + std::to_string(mm) + ":" + std::to_string(ss)}); + return ret; + } + + [[nodiscard]] std::string to_string() const { + return + "{" + std::to_string(daysec) + "," + std::to_string(nanosec) + "}_" + + "[" + + std::to_string(hh) + "时:" + + std::to_string(mm) + "分:" + + std::to_string(ss) + "秒" + "]"; + } + + void parse_hhmmss(); +}; + +#endif diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/export.h b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/export.h new file mode 100644 index 0000000..2150056 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/export.h @@ -0,0 +1,114 @@ +#ifndef yc_magic_enum_H +#define yc_magic_enum_H +#include "magic_enum/magic_enum.hpp" +#include +#include + +template +EnumType to_enum(const std::string& str) { + auto opt = magic_enum::enum_cast(str); + if (opt.has_value()) { + return opt.value(); // 返回枚举值 + } + std::cerr << std::string(magic_enum::enum_type_name()) +" 字符串转换枚举值错误!str:" + str + "\n"; + exit(-1); +} + +template +std::optional to_enum2(const std::string& str) { + auto opt = magic_enum::enum_cast(str); + if (opt.has_value()) { + return opt.value(); // 返回枚举值 + } + return std::nullopt; +} + +template +std::string to_string(EnumType type) { + return std::string(magic_enum::enum_name(type)); +} + +template +std::optional int_to_enum(int enum_value) { + // 静态断言,确保 EnumType 是枚举类型 + static_assert(std::is_enum_v, "EnumType must be an enum type"); + // 使用 magic_enum 获取所有的枚举值 + auto enum_values = magic_enum::enum_values(); + // 检查是否存在匹配的枚举值 + for (auto value : enum_values) { + if (static_cast(value) == enum_value) { + return value; // 返回匹配的枚举值 + } + } + // 如果没有匹配的枚举值,返回 std::nullopt + return std::nullopt; +} + +template +std::optional enum_int_to_string(int enum_value) { + auto tmp = int_to_enum(enum_value); + if (!tmp.has_value()) return std::nullopt; + return to_string(tmp.value()); +} + +template +std::vector split_flags(Enum flags) +{ + static_assert(std::is_enum_v, "Template parameter must be an enum type"); + std::vector setFlags; + for (auto flag : magic_enum::enum_values()) + { + if ((flags & flag) == flag) + { + setFlags.push_back(flag); + } + } + return setFlags; +} + + +inline std::string to_string(const char* val) { + return std::string(val); +} + +template <> +inline std::string to_string(std::string val) { + return val; +} +template <> +inline std::string to_string(bool val) { + return val ? "true" : "false"; +} +template <> +inline std::string to_string(void* val) { + std::ostringstream oss; + oss << "0x" << std::hex << std::showbase << reinterpret_cast(val); + return oss.str(); +} + +#define IMPL_to_string_from_std(Type) template <> \ +inline std::string to_string(Type val) { \ + return std::to_string(val); \ +} \ + +IMPL_to_string_from_std(signed char) +IMPL_to_string_from_std(short) +IMPL_to_string_from_std(int) +IMPL_to_string_from_std(long) +IMPL_to_string_from_std(long long) +IMPL_to_string_from_std(unsigned char) +IMPL_to_string_from_std(unsigned short) +IMPL_to_string_from_std(unsigned int) +IMPL_to_string_from_std(unsigned long) +IMPL_to_string_from_std(unsigned long long) +IMPL_to_string_from_std(float) +IMPL_to_string_from_std(double) +IMPL_to_string_from_std(long double) +#undef IMPL_to_string_from_std + + + + + + +#endif \ No newline at end of file diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/LICENSE b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/LICENSE new file mode 100644 index 0000000..20acf2f --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 - 2024 Daniil Goncharov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum.hpp b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum.hpp new file mode 100644 index 0000000..158207e --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum.hpp @@ -0,0 +1,1508 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_HPP +#define NEARGYE_MAGIC_ENUM_HPP + +#define MAGIC_ENUM_VERSION_MAJOR 0 +#define MAGIC_ENUM_VERSION_MINOR 9 +#define MAGIC_ENUM_VERSION_PATCH 7 + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(MAGIC_ENUM_CONFIG_FILE) +# include MAGIC_ENUM_CONFIG_FILE +#endif + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +# include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING) +# include +#endif +#if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +# include +#endif +#endif + +#if defined(MAGIC_ENUM_NO_ASSERT) +# define MAGIC_ENUM_ASSERT(...) static_cast(0) +#elif !defined(MAGIC_ENUM_ASSERT) +# include +# define MAGIC_ENUM_ASSERT(...) assert((__VA_ARGS__)) +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunknown-warning-option" +# pragma clang diagnostic ignored "-Wenum-constexpr-conversion" +# pragma clang diagnostic ignored "-Wuseless-cast" // suppresses 'static_cast('\0')' for char_type = char (common on Linux). +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. +# pragma GCC diagnostic ignored "-Wuseless-cast" // suppresses 'static_cast('\0')' for char_type = char (common on Linux). +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 26495) // Variable 'static_str::chars_' is uninitialized. +# pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. +# pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call. +# pragma warning(disable : 4514) // Unreferenced inline function has been removed. +#endif + +// Checks magic_enum compiler compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 || defined(__RESHARPER__) +# undef MAGIC_ENUM_SUPPORTED +# define MAGIC_ENUM_SUPPORTED 1 +#endif + +// Checks magic_enum compiler aliases compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920 +# undef MAGIC_ENUM_SUPPORTED_ALIASES +# define MAGIC_ENUM_SUPPORTED_ALIASES 1 +#endif + +// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128. +// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN. +#if !defined(MAGIC_ENUM_RANGE_MIN) +# define MAGIC_ENUM_RANGE_MIN -128 +#endif + +// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 127. +// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX. +#if !defined(MAGIC_ENUM_RANGE_MAX) +# define MAGIC_ENUM_RANGE_MAX 127 +#endif + +// Improve ReSharper C++ intellisense performance with builtins, avoiding unnecessary template instantiations. +#if defined(__RESHARPER__) +# undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +# undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +# if __RESHARPER__ >= 20230100 +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) __rscpp_enumerator_name(V) +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) __rscpp_type_name() +# else +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) nullptr +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) nullptr +# endif +#endif + +namespace magic_enum { + +// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL. +#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +MAGIC_ENUM_USING_ALIAS_OPTIONAL +#else +using std::optional; +#endif + +// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +MAGIC_ENUM_USING_ALIAS_STRING_VIEW +#else +using std::string_view; +#endif + +// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING) +MAGIC_ENUM_USING_ALIAS_STRING +#else +using std::string; +#endif + +using char_type = string_view::value_type; +static_assert(std::is_same_v, "magic_enum::customize requires same string_view::value_type and string::value_type"); +static_assert([] { + if constexpr (std::is_same_v) { + constexpr const char c[] = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + constexpr const wchar_t wc[] = L"abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + static_assert(std::size(c) == std::size(wc), "magic_enum::customize identifier characters are multichars in wchar_t."); + + for (std::size_t i = 0; i < std::size(c); ++i) { + if (c[i] != wc[i]) { + return false; + } + } + } + return true; +} (), "magic_enum::customize wchar_t is not compatible with ASCII."); + +namespace customize { + +// Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 127. +// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX. +// If need another range for specific enum type, add specialization enum_range for necessary enum type. +template +struct enum_range { + static constexpr int min = MAGIC_ENUM_RANGE_MIN; + static constexpr int max = MAGIC_ENUM_RANGE_MAX; +}; + +static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); + +namespace detail { + +enum class customize_tag { + default_tag, + invalid_tag, + custom_tag +}; + +} // namespace magic_enum::customize::detail + +class customize_t : public std::pair { + public: + constexpr customize_t(string_view srt) : std::pair{detail::customize_tag::custom_tag, srt} {} + constexpr customize_t(const char_type* srt) : customize_t{string_view{srt}} {} + constexpr customize_t(detail::customize_tag tag) : std::pair{tag, string_view{}} { + MAGIC_ENUM_ASSERT(tag != detail::customize_tag::custom_tag); + } +}; + +// Default customize. +inline constexpr auto default_tag = customize_t{detail::customize_tag::default_tag}; +// Invalid customize. +inline constexpr auto invalid_tag = customize_t{detail::customize_tag::invalid_tag}; + +// If need custom names for enum, add specialization enum_name for necessary enum type. +template +constexpr customize_t enum_name(E) noexcept { + return default_tag; +} + +// If need custom type name for enum, add specialization enum_type_name for necessary enum type. +template +constexpr customize_t enum_type_name() noexcept { + return default_tag; +} + +} // namespace magic_enum::customize + +namespace detail { + +template +struct supported +#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED || defined(MAGIC_ENUM_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template , std::enable_if_t, int> = 0> +using enum_constant = std::integral_constant; + +template +inline constexpr bool always_false_v = false; + +template +struct has_is_flags : std::false_type {}; + +template +struct has_is_flags::is_flags)>> : std::bool_constant::is_flags)>>> {}; + +template +struct range_min : std::integral_constant {}; + +template +struct range_min::min)>> : std::integral_constant::min), customize::enum_range::min> {}; + +template +struct range_max : std::integral_constant {}; + +template +struct range_max::max)>> : std::integral_constant::max), customize::enum_range::max> {}; + +struct str_view { + const char* str_ = nullptr; + std::size_t size_ = 0; +}; + +template +class static_str { + public: + constexpr explicit static_str(str_view str) noexcept : static_str{str.str_, std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size_ == N); + } + + constexpr explicit static_str(string_view str) noexcept : static_str{str.data(), std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size() == N); + } + + constexpr const char_type* data() const noexcept { return chars_; } + + constexpr std::uint16_t size() const noexcept { return N; } + + constexpr operator string_view() const noexcept { return {data(), size()}; } + + private: + template + constexpr static_str(const char* str, std::integer_sequence) noexcept : chars_{static_cast(str[I])..., static_cast('\0')} {} + + template + constexpr static_str(string_view str, std::integer_sequence) noexcept : chars_{str[I]..., static_cast('\0')} {} + + char_type chars_[static_cast(N) + 1]; +}; + +template <> +class static_str<0> { + public: + constexpr explicit static_str() = default; + + constexpr explicit static_str(str_view) noexcept {} + + constexpr explicit static_str(string_view) noexcept {} + + constexpr const char_type* data() const noexcept { return nullptr; } + + constexpr std::uint16_t size() const noexcept { return 0; } + + constexpr operator string_view() const noexcept { return {}; } +}; + +template > +class case_insensitive { + static constexpr char_type to_lower(char_type c) noexcept { + return (c >= static_cast('A') && c <= static_cast('Z')) ? static_cast(c + (static_cast('a') - static_cast('A'))) : c; + } + + public: + template + constexpr auto operator()(L lhs, R rhs) const noexcept -> std::enable_if_t, char_type> && std::is_same_v, char_type>, bool> { + return Op{}(to_lower(lhs), to_lower(rhs)); + } +}; + +constexpr std::size_t find(string_view str, char_type c) noexcept { +#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) +// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc +// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (workaround) { + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == c) { + return i; + } + } + + return string_view::npos; + } else { + return str.find(c); + } +} + +template +constexpr bool is_default_predicate() noexcept { + return std::is_same_v, std::equal_to> || + std::is_same_v, std::equal_to<>>; +} + +template +constexpr bool is_nothrow_invocable() { + return is_default_predicate() || + std::is_nothrow_invocable_r_v; +} + +template +constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(is_nothrow_invocable()) { +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (!is_default_predicate() || workaround) { + if (lhs.size() != rhs.size()) { + return false; + } + + const auto size = lhs.size(); + for (std::size_t i = 0; i < size; ++i) { + if (!p(lhs[i], rhs[i])) { + return false; + } + } + + return true; + } else { + return lhs == rhs; + } +} + +template +constexpr bool cmp_less(L lhs, R rhs) noexcept { + static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); + + if constexpr (std::is_signed_v == std::is_signed_v) { + // If same signedness (both signed or both unsigned). + return lhs < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return static_cast(lhs) < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return lhs < static_cast(rhs); + } else if constexpr (std::is_signed_v) { + // If 'right' is negative, then result is 'false', otherwise cast & compare. + return rhs > 0 && lhs < static_cast>(rhs); + } else { + // If 'left' is negative, then result is 'true', otherwise cast & compare. + return lhs < 0 || static_cast>(lhs) < rhs; + } +} + +template +constexpr I log2(I value) noexcept { + static_assert(std::is_integral_v, "magic_enum::detail::log2 requires integral type."); + + if constexpr (std::is_same_v) { // bool special case + return MAGIC_ENUM_ASSERT(false), value; + } else { + auto ret = I{0}; + for (; value > I{1}; value >>= I{1}, ++ret) {} + + return ret; + } +} + +#if defined(__cpp_lib_array_constexpr) && __cpp_lib_array_constexpr >= 201603L +# define MAGIC_ENUM_ARRAY_CONSTEXPR 1 +#else +template +constexpr std::array, N> to_array(T (&a)[N], std::index_sequence) noexcept { + return {{a[I]...}}; +} +#endif + +template +inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_GET_TYPE_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(E); + constexpr auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +#elif defined(__clang__) + str_view name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } +#elif defined(__GNUC__) + auto name = str_view{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 50; + name.str_ += 49; + } else { + name.size_ -= 40; + name.str_ += 37; + } +#elif defined(_MSC_VER) + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + str_view name; + name.str_ = __FUNCSIG__; + name.str_ += 40; + name.size_ += sizeof(__FUNCSIG__) - 57; +#else + auto name = str_view{}; +#endif + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + return name; + } else { + return str_view{}; // Unsupported compiler or Invalid customize. + } +} + +template +constexpr auto type_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_type_name(); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { + constexpr auto name = n(); + return static_str{name}; + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } +} + +template +inline constexpr auto type_name_v = type_name(); + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +#elif defined(__clang__) + str_view name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } + if (name.size_ > 22 && name.str_[0] == '(' && name.str_[1] == 'a' && name.str_[10] == ' ' && name.str_[22] == ':') { + name.size_ -= 23; + name.str_ += 23; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = str_view{}; + } +#elif defined(__GNUC__) + auto name = str_view{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 55; + name.str_ += 54; + } else { + name.size_ -= 40; + name.str_ += 37; + } + if (name.str_[0] == '(') { + name = str_view{}; + } +#elif defined(_MSC_VER) + str_view name; + if ((__FUNCSIG__[5] == '_' && __FUNCSIG__[35] != '(') || (__FUNCSIG__[5] == 'c' && __FUNCSIG__[41] != '(')) { + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + name.str_ = __FUNCSIG__; + name.str_ += 35; + name.size_ = sizeof(__FUNCSIG__) - 52; + } +#else + auto name = str_view{}; +#endif + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + return name; + } else { + return str_view{}; // Unsupported compiler or Invalid customize. + } +} + +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920 +# define MAGIC_ENUM_VS_2017_WORKAROUND 1 +#endif + +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + +# if defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +# else + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + str_view name; + name.str_ = __FUNCSIG__; + name.size_ = sizeof(__FUNCSIG__) - 17; + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ',' || name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = str_view{}; + } + return name; +# endif +} +#endif + +template +constexpr auto enum_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_name(V); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + constexpr auto name = n(); +#else + constexpr auto name = n(); +#endif + return static_str{name}; + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } +} + +template +inline constexpr auto enum_name_v = enum_name(); + +template +constexpr bool is_valid() noexcept { +#if defined(__clang__) && __clang_major__ >= 16 + // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 + constexpr E v = __builtin_bit_cast(E, V); +#else + constexpr E v = static_cast(V); +#endif + [[maybe_unused]] constexpr auto custom = customize::enum_name(v); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return name.size() != 0; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + return n().size_ != 0; +#else + return n().size_ != 0; +#endif + } else { + return false; + } +} + +enum class enum_subtype { + common, + flags +}; + +template > +constexpr U ualue(std::size_t i) noexcept { + if constexpr (std::is_same_v) { // bool special case + static_assert(O == 0, "magic_enum::detail::ualue requires valid offset."); + + return static_cast(i); + } else if constexpr (S == enum_subtype::flags) { + return static_cast(U{1} << static_cast(static_cast(i) + O)); + } else { + return static_cast(static_cast(i) + O); + } +} + +template > +constexpr E value(std::size_t i) noexcept { + return static_cast(ualue(i)); +} + +template > +constexpr int reflected_min() noexcept { + if constexpr (S == enum_subtype::flags) { + return 0; + } else { + constexpr auto lhs = range_min::value; + constexpr auto rhs = (std::numeric_limits::min)(); + + if constexpr (cmp_less(rhs, lhs)) { + return lhs; + } else { + return rhs; + } + } +} + +template > +constexpr int reflected_max() noexcept { + if constexpr (S == enum_subtype::flags) { + return std::numeric_limits::digits - 1; + } else { + constexpr auto lhs = range_max::value; + constexpr auto rhs = (std::numeric_limits::max)(); + + if constexpr (cmp_less(lhs, rhs)) { + return lhs; + } else { + return rhs; + } + } +} + +#define MAGIC_ENUM_FOR_EACH_256(T) \ + T( 0)T( 1)T( 2)T( 3)T( 4)T( 5)T( 6)T( 7)T( 8)T( 9)T( 10)T( 11)T( 12)T( 13)T( 14)T( 15)T( 16)T( 17)T( 18)T( 19)T( 20)T( 21)T( 22)T( 23)T( 24)T( 25)T( 26)T( 27)T( 28)T( 29)T( 30)T( 31) \ + T( 32)T( 33)T( 34)T( 35)T( 36)T( 37)T( 38)T( 39)T( 40)T( 41)T( 42)T( 43)T( 44)T( 45)T( 46)T( 47)T( 48)T( 49)T( 50)T( 51)T( 52)T( 53)T( 54)T( 55)T( 56)T( 57)T( 58)T( 59)T( 60)T( 61)T( 62)T( 63) \ + T( 64)T( 65)T( 66)T( 67)T( 68)T( 69)T( 70)T( 71)T( 72)T( 73)T( 74)T( 75)T( 76)T( 77)T( 78)T( 79)T( 80)T( 81)T( 82)T( 83)T( 84)T( 85)T( 86)T( 87)T( 88)T( 89)T( 90)T( 91)T( 92)T( 93)T( 94)T( 95) \ + T( 96)T( 97)T( 98)T( 99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \ + T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \ + T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \ + T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \ + T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255) + +template +constexpr void valid_count(bool* valid, std::size_t& count) noexcept { +#define MAGIC_ENUM_V(O) \ + if constexpr ((I + O) < Size) { \ + if constexpr (is_valid(I + O)>()) { \ + valid[I + O] = true; \ + ++count; \ + } \ + } + + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_V) + + if constexpr ((I + 256) < Size) { + valid_count(valid, count); + } +#undef MAGIC_ENUM_V +} + +template +struct valid_count_t { + std::size_t count = 0; + bool valid[N] = {}; +}; + +template +constexpr auto valid_count() noexcept { + valid_count_t vc; + valid_count(vc.valid, vc.count); + return vc; +} + +template +constexpr auto values() noexcept { + constexpr auto vc = valid_count(); + + if constexpr (vc.count > 0) { +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + std::array values = {}; +#else + E values[vc.count] = {}; +#endif + for (std::size_t i = 0, v = 0; v < vc.count; ++i) { + if (vc.valid[i]) { + values[v++] = value(i); + } + } +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + return values; +#else + return to_array(values, std::make_index_sequence{}); +#endif + } else { + return std::array{}; + } +} + +template > +constexpr auto values() noexcept { + constexpr auto min = reflected_min(); + constexpr auto max = reflected_max(); + constexpr auto range_size = max - min + 1; + static_assert(range_size > 0, "magic_enum::enum_range requires valid size."); + + return values(); +} + +template > +constexpr enum_subtype subtype(std::true_type) noexcept { + if constexpr (std::is_same_v) { // bool special case + return enum_subtype::common; + } else if constexpr (has_is_flags::value) { + return customize::enum_range::is_flags ? enum_subtype::flags : enum_subtype::common; + } else { +#if defined(MAGIC_ENUM_AUTO_IS_FLAGS) + constexpr auto flags_values = values(); + constexpr auto default_values = values(); + if (flags_values.size() == 0 || default_values.size() > flags_values.size()) { + return enum_subtype::common; + } + for (std::size_t i = 0; i < default_values.size(); ++i) { + const auto v = static_cast(default_values[i]); + if (v != 0 && (v & (v - 1)) != 0) { + return enum_subtype::common; + } + } + return enum_subtype::flags; +#else + return enum_subtype::common; +#endif + } +} + +template +constexpr enum_subtype subtype(std::false_type) noexcept { + // For non-enum type return default common subtype. + return enum_subtype::common; +} + +template > +inline constexpr auto subtype_v = subtype(std::is_enum{}); + +template +inline constexpr auto values_v = values(); + +template > +using values_t = decltype((values_v)); + +template +inline constexpr auto count_v = values_v.size(); + +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; + +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; + +template +constexpr auto names(std::index_sequence) noexcept { + constexpr auto names = std::array{{enum_name_v[I]>...}}; + return names; +} + +template +inline constexpr auto names_v = names(std::make_index_sequence>{}); + +template > +using names_t = decltype((names_v)); + +template +constexpr auto entries(std::index_sequence) noexcept { + constexpr auto entries = std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; + return entries; +} + +template +inline constexpr auto entries_v = entries(std::make_index_sequence>{}); + +template > +using entries_t = decltype((entries_v)); + +template > +constexpr bool is_sparse() noexcept { + if constexpr (count_v == 0) { + return false; + } else if constexpr (std::is_same_v) { // bool special case + return false; + } else { + constexpr auto max = (S == enum_subtype::flags) ? log2(max_v) : max_v; + constexpr auto min = (S == enum_subtype::flags) ? log2(min_v) : min_v; + constexpr auto range_size = max - min + 1; + + return range_size != count_v; + } +} + +template > +inline constexpr bool is_sparse_v = is_sparse(); + +template +struct is_reflected +#if defined(MAGIC_ENUM_NO_CHECK_REFLECTED_ENUM) + : std::true_type {}; +#else + : std::bool_constant && (count_v != 0)> {}; +#endif + +template +inline constexpr bool is_reflected_v = is_reflected, S>{}; + +template +struct enable_if_enum {}; + +template +struct enable_if_enum { + using type = R; + static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); +}; + +template , typename D = std::decay_t> +using enable_if_t = typename enable_if_enum && std::is_invocable_r_v, R>::type; + +template >, int> = 0> +using enum_concept = T; + +template > +struct is_scoped_enum : std::false_type {}; + +template +struct is_scoped_enum : std::bool_constant>> {}; + +template > +struct is_unscoped_enum : std::false_type {}; + +template +struct is_unscoped_enum : std::bool_constant>> {}; + +template >> +struct underlying_type {}; + +template +struct underlying_type : std::underlying_type> {}; + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + +template +struct constexpr_hash_t; + +template +struct constexpr_hash_t>> { + constexpr auto operator()(Value value) const noexcept { + using U = typename underlying_type::type; + if constexpr (std::is_same_v) { // bool special case + return static_cast(value); + } else { + return static_cast(value); + } + } + using secondary_hash = constexpr_hash_t; +}; + +template +struct constexpr_hash_t>> { + static constexpr std::uint32_t crc_table[256] { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL + }; + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto crc = static_cast(0xffffffffL); + for (const auto c : value) { + crc = (crc >> 8) ^ crc_table[(crc ^ static_cast(c)) & 0xff]; + } + return crc ^ 0xffffffffL; + } + + struct secondary_hash { + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto acc = static_cast(2166136261ULL); + for (const auto c : value) { + acc = ((acc ^ static_cast(c)) * static_cast(16777619ULL)) & (std::numeric_limits::max)(); + } + return static_cast(acc); + } + }; +}; + +template +inline constexpr Hash hash_v{}; + +template +constexpr auto calculate_cases(std::size_t Page) noexcept { + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + + using switch_t = std::invoke_result_t; + static_assert(std::is_integral_v && !std::is_same_v); + const std::size_t values_to = (std::min)(static_cast(256), size - Page); + + std::array result{}; + auto fill = result.begin(); + { + auto first = values.begin() + static_cast(Page); + auto last = values.begin() + static_cast(Page + values_to); + while (first != last) { + *fill++ = hash_v(*first++); + } + } + + // dead cases, try to avoid case collisions + for (switch_t last_value = result[values_to - 1]; fill != result.end() && last_value != (std::numeric_limits::max)(); *fill++ = ++last_value) { + } + + { + auto it = result.begin(); + auto last_value = (std::numeric_limits::min)(); + for (; fill != result.end(); *fill++ = last_value++) { + while (last_value == *it) { + ++last_value, ++it; + } + } + } + + return result; +} + +template +constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v) { + if constexpr (std::is_void_v) { + std::forward(f)(std::forward(args)...); + } else { + return static_cast(std::forward(f)(std::forward(args)...)); + } +} + +enum class case_call_t { + index, + value +}; + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr bool has_duplicate() noexcept { + using value_t = std::decay_t; + using hash_value_t = std::invoke_result_t; + std::arraysize()> hashes{}; + std::size_t size = 0; + for (auto elem : *Arr) { + hashes[size] = hash_v(elem); + for (auto i = size++; i > 0; --i) { + if (hashes[i] < hashes[i - 1]) { + auto tmp = hashes[i]; + hashes[i] = hashes[i - 1]; + hashes[i - 1] = tmp; + } else if (hashes[i] == hashes[i - 1]) { + return false; + } else { + break; + } + } + } + return true; +} + +#define MAGIC_ENUM_CASE(val) \ + case cases[val]: \ + if constexpr ((val) + Page < size) { \ + if (!pred(values[val + Page], searched)) { \ + break; \ + } \ + if constexpr (CallValue == case_call_t::index) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ + } else if constexpr (std::is_invocable_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } else if constexpr (CallValue == case_call_t::value) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), enum_constant{}); \ + } else if constexpr (std::is_invocable_r_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } \ + break; \ + } else [[fallthrough]]; + +template ::value_type>, + typename BinaryPredicate = std::equal_to<>, + typename Lambda, + typename ResultGetterType> +constexpr decltype(auto) constexpr_switch( + Lambda&& lambda, + typename std::decay_t::value_type searched, + ResultGetterType&& def, + BinaryPredicate&& pred = {}) { + using result_t = std::invoke_result_t; + using hash_t = std::conditional_t(), Hash, typename Hash::secondary_hash>; + static_assert(has_duplicate(), "magic_enum::detail::constexpr_switch duplicated hash found, please report it: https://github.com/Neargye/magic_enum/issues."); + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + constexpr std::array cases = calculate_cases(Page); + + switch (hash_v(searched)) { + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_CASE) + default: + if constexpr (size > 256 + Page) { + return constexpr_switch(std::forward(lambda), searched, std::forward(def)); + } + break; + } + return def(); +} + +#undef MAGIC_ENUM_CASE + +#endif + +} // namespace magic_enum::detail + +// Checks is magic_enum supported compiler. +inline constexpr bool is_magic_enum_supported = detail::supported::value; + +template +using Enum = detail::enum_concept; + +// Checks whether T is an Unscoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) type. Otherwise, value is equal to false. +template +struct is_unscoped_enum : detail::is_unscoped_enum {}; + +template +inline constexpr bool is_unscoped_enum_v = is_unscoped_enum::value; + +// Checks whether T is an Scoped enumeration type. +// Provides the member constant value which is equal to true, if T is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) type. Otherwise, value is equal to false. +template +struct is_scoped_enum : detail::is_scoped_enum {}; + +template +inline constexpr bool is_scoped_enum_v = is_scoped_enum::value; + +// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T. +// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed. +template +struct underlying_type : detail::underlying_type {}; + +template +using underlying_type_t = typename underlying_type::type; + +template +using enum_constant = detail::enum_constant; + +// Returns type name of enum. +template +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::type_name_v>; + static_assert(!name.empty(), "magic_enum::enum_type_name enum type does not have a name."); + + return name; +} + +// Returns number of enum values. +template > +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_t { + return detail::count_v, S>; +} + +// Returns enum value at specified index. +// No bounds checking is performed: the behavior is undefined if index >= number of enum values. +template > +[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v) { + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::values_v[index]; + } else { + constexpr auto min = (S == detail::enum_subtype::flags) ? detail::log2(detail::min_v) : detail::min_v; + + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::value(index); + } +} + +// Returns enum value at specified index. +template > +[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + static_assert(I < detail::count_v, "magic_enum::enum_value out of range."); + + return enum_value(I); +} + +// Returns std::array with enum values, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::values_v; +} + +// Returns integer value from enum value. +template +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); +} + +// Returns underlying value from enum value. +template +[[nodiscard]] constexpr auto enum_underlying(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); +} + +// Obtains index in enum values from enum value. +// Returns optional with index. +template > +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + using U = underlying_type_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{i}; }, + value, + detail::default_result_type_lambda>); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (enum_value(i) == value) { + return i; + } + } + return {}; // Invalid value or out of range. +#endif + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return static_cast(v - detail::min_v); + } + return {}; // Invalid value or out of range. + } +} + +// Obtains index in enum values from enum value. +// Returns optional with index. +template +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return enum_index(value); +} + +// Obtains index in enum values from static storage enum variable. +template >> +[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t {\ + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + constexpr auto index = enum_index(V); + static_assert(index, "magic_enum::enum_index enum value does not have a index."); + + return *index; +} + +// Returns name from static storage enum variable. +// This version is much lighter on the compile times and is not restricted to the enum_range limitation. +template +[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::enum_name_v, V>; + static_assert(!name.empty(), "magic_enum::enum_name enum value does not have a name."); + + return name; +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template > +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if (const auto i = enum_index(value)) { + return detail::names_v[*i]; + } + return {}; +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template +[[nodiscard]] constexpr auto enum_name(E value) -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return enum_name(value); +} + +// Returns std::array with names, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::names_v; +} + +// Returns std::array with pairs (value, name), sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::entries_v; +} + +// Allows you to write magic_enum::enum_cast("bar", magic_enum::case_insensitive); +inline constexpr auto case_insensitive = detail::case_insensitive<>{}; + +// Obtains enum value from integer value. +// Returns optional with enum value. +template > +[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + [](D v) { return optional{v}; }, + static_cast(value), + detail::default_result_type_lambda>); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (value == static_cast>(enum_value(i))) { + return static_cast(value); + } + } + return {}; // Invalid value or out of range. +#endif + } else { + if (value >= detail::min_v && value <= detail::max_v) { + return static_cast(value); + } + return {}; // Invalid value or out of range. + } +} + +// Obtains enum value from name. +// Returns optional with enum value. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) + if constexpr (detail::is_default_predicate()) { + return detail::constexpr_switch<&detail::names_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{detail::values_v[i]}; }, + value, + detail::default_result_type_lambda>, + [&p](string_view lhs, string_view rhs) { return detail::cmp_equal(lhs, rhs, p); }); + } +#endif + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(value, detail::names_v[i], p)) { + return enum_value(i); + } + } + return {}; // Invalid value or out of range. +} + +// Checks whether enum contains value with such value. +template > +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_cast(static_cast(value))); +} + +// Checks whether enum contains value with such value. +template +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_cast(static_cast(value))); +} + +// Checks whether enum contains value with such integer value. +template > +[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_cast(value)); +} + +// Checks whether enum contains enumerator with such name. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_cast(value, std::move(p))); +} + +// Returns true if the enum integer value is in the range of values that can be reflected. +template > +[[nodiscard]] constexpr auto enum_reflected(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + if constexpr (detail::is_reflected_v) { + constexpr auto min = detail::reflected_min(); + constexpr auto max = detail::reflected_max(); + return value >= min && value <= max; + } else { + return false; + } +} + +// Returns true if the enum value is in the range of values that can be reflected. +template > +[[nodiscard]] constexpr auto enum_reflected(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return enum_reflected(static_cast>(value)); +} + +// Returns true if the enum value is in the range of values that can be reflected. +template +[[nodiscard]] constexpr auto enum_reflected(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return enum_reflected(value); +} + +template +inline constexpr auto as_flags = AsFlags ? detail::enum_subtype::flags : detail::enum_subtype::common; + +template +inline constexpr auto as_common = AsFlags ? detail::enum_subtype::common : detail::enum_subtype::flags; + +namespace bitwise_operators { + +template = 0> +constexpr E operator~(E rhs) noexcept { + return static_cast(~static_cast>(rhs)); +} + +template = 0> +constexpr E operator|(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} + +template = 0> +constexpr E operator&(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +template = 0> +constexpr E operator^(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); +} + +template = 0> +constexpr E& operator|=(E& lhs, E rhs) noexcept { + return lhs = (lhs | rhs); +} + +template = 0> +constexpr E& operator&=(E& lhs, E rhs) noexcept { + return lhs = (lhs & rhs); +} + +template = 0> +constexpr E& operator^=(E& lhs, E rhs) noexcept { + return lhs = (lhs ^ rhs); +} + +} // namespace magic_enum::bitwise_operators + +} // namespace magic_enum + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +#undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +#undef MAGIC_ENUM_VS_2017_WORKAROUND +#undef MAGIC_ENUM_ARRAY_CONSTEXPR +#undef MAGIC_ENUM_FOR_EACH_256 + +#endif // NEARGYE_MAGIC_ENUM_HPP diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_all.hpp b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_all.hpp new file mode 100644 index 0000000..7a05281 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_all.hpp @@ -0,0 +1,44 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_ALL_HPP +#define NEARGYE_MAGIC_ENUM_ALL_HPP + +#include "magic_enum.hpp" +#include "../magic_enum_containers.hpp" +#include "../magic_enum_flags.hpp" +#include "../magic_enum_format.hpp" +#include "../magic_enum_fuse.hpp" +#include "../magic_enum_iostream.hpp" +#include "../magic_enum_switch.hpp" +#include "../magic_enum_utility.hpp" + +#endif // NEARGYE_MAGIC_ENUM_ALL_HPP diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_containers.hpp b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_containers.hpp new file mode 100644 index 0000000..f817306 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_containers.hpp @@ -0,0 +1,1174 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// Copyright (c) 2022 - 2023 Bela Schaum . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_CONTAINERS_HPP +#define NEARGYE_MAGIC_ENUM_CONTAINERS_HPP + +#include "magic_enum.hpp" + +#if !defined(MAGIC_ENUM_NO_EXCEPTION) && (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +#endif +# define MAGIC_ENUM_THROW(...) throw (__VA_ARGS__) +#else +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +#endif +# define MAGIC_ENUM_THROW(...) std::abort() +#endif + +namespace magic_enum::containers { + +namespace detail { + +template +static constexpr bool is_transparent_v{}; + +template +static constexpr bool is_transparent_v>{true}; + +template , typename T1, typename T2> +constexpr bool equal(T1&& t1, T2&& t2, Eq&& eq = {}) { + auto first1 = t1.begin(); + auto last1 = t1.end(); + auto first2 = t2.begin(); + auto last2 = t2.end(); + + for (; first1 != last1; ++first1, ++first2) { + if (first2 == last2 || !eq(*first1, *first2)) { + return false; + } + } + return first2 == last2; +} + +template , typename T1, typename T2> +constexpr bool lexicographical_compare(T1&& t1, T2&& t2, Cmp&& cmp = {}) noexcept { + auto first1 = t1.begin(); + auto last1 = t1.end(); + auto first2 = t2.begin(); + auto last2 = t2.end(); + + // copied from std::lexicographical_compare + for (; (first1 != last1) && (first2 != last2); ++first1, (void)++first2) { + if (cmp(*first1, *first2)) { + return true; + } + if (cmp(*first2, *first1)) { + return false; + } + } + return (first1 == last1) && (first2 != last2); +} + +template +constexpr std::size_t popcount(T x) noexcept { + std::size_t c = 0; + while (x > 0) { + c += x & 1; + x >>= 1; + } + return c; +} + +template , typename ForwardIt, typename E> +constexpr ForwardIt lower_bound(ForwardIt first, ForwardIt last, E&& e, Cmp&& comp = {}) { + auto count = std::distance(first, last); + for (auto it = first; count > 0;) { + auto step = count / 2; + std::advance(it, step); + if (comp(*it, e)) { + first = ++it; + count -= step + 1; + } else { + count = step; + } + } + return first; +} + +template , typename BidirIt, typename E> +constexpr auto equal_range(BidirIt begin, BidirIt end, E&& e, Cmp&& comp = {}) { + const auto first = lower_bound(begin, end, e, comp); + return std::pair{first, lower_bound(std::make_reverse_iterator(end), std::make_reverse_iterator(first), e, [&comp](auto&& lhs, auto&& rhs) { return comp(rhs, lhs); }).base()}; +} + +template , typename = void> +class indexing { + [[nodiscard]] static constexpr auto get_indices() noexcept { + // reverse result index mapping + std::array()> rev_res{}; + + // std::iota + for (std::size_t i = 0; i < enum_count(); ++i) { + rev_res[i] = i; + } + + constexpr auto orig_values = enum_values(); + constexpr Cmp cmp{}; + + // ~std::sort + for (std::size_t i = 0; i < enum_count(); ++i) { + for (std::size_t j = i + 1; j < enum_count(); ++j) { + if (cmp(orig_values[rev_res[j]], orig_values[rev_res[i]])) { + auto tmp = rev_res[i]; + rev_res[i] = rev_res[j]; + rev_res[j] = tmp; + } + } + } + + std::array()> sorted_values{}; + // reverse the sorted indices + std::array()> res{}; + for (std::size_t i = 0; i < enum_count(); ++i) { + res[rev_res[i]] = i; + sorted_values[i] = orig_values[rev_res[i]]; + } + + return std::pair{sorted_values, res}; + } + + static constexpr auto indices = get_indices(); + + public: + [[nodiscard]] static constexpr const E* begin() noexcept { return indices.first.data(); } + + [[nodiscard]] static constexpr const E* end() noexcept { return indices.first.data() + indices.first.size(); } + + [[nodiscard]] static constexpr const E* it(std::size_t i) noexcept { return indices.first.data() + i; } + + [[nodiscard]] static constexpr optional at(E val) noexcept { + if (auto i = enum_index(val)) { + return indices.second[*i]; + } + return {}; + } +}; + +template +class indexing> && (std::is_same_v> || std::is_same_v>)>> { + static constexpr auto& values = enum_values(); + + public: + [[nodiscard]] static constexpr const E* begin() noexcept { return values.data(); } + + [[nodiscard]] static constexpr const E* end() noexcept { return values.data() + values.size(); } + + [[nodiscard]] static constexpr const E* it(std::size_t i) noexcept { return values.data() + i; } + + [[nodiscard]] static constexpr optional at(E val) noexcept { return enum_index(val); } +}; + +template +struct indexing { + using is_transparent = std::true_type; + + template + [[nodiscard]] static constexpr optional at(E val) noexcept { + return indexing::at(val); + } +}; + +template , typename = void> +struct name_sort_impl { + [[nodiscard]] constexpr bool operator()(E e1, E e2) const noexcept { return Cmp{}(enum_name(e1), enum_name(e2)); } +}; + +template +struct name_sort_impl { + using is_transparent = std::true_type; + + template + struct FullCmp : C {}; + + template + struct FullCmp && std::is_invocable_v>> { + [[nodiscard]] constexpr bool operator()(string_view s1, string_view s2) const noexcept { return lexicographical_compare(s1, s2); } + }; + + template + [[nodiscard]] constexpr std::enable_if_t< + // at least one of need to be an enum type + (std::is_enum_v> || std::is_enum_v>) && + // if both is enum, only accept if the same enum + (!std::is_enum_v> || !std::is_enum_v> || std::is_same_v) && + // is invocable with comparator + (std::is_invocable_r_v, std::conditional_t>, string_view, E1>, std::conditional_t>, string_view, E2>>), + bool> + operator()(E1 e1, E2 e2) const noexcept { + using D1 = std::decay_t; + using D2 = std::decay_t; + constexpr FullCmp<> cmp{}; + + if constexpr (std::is_enum_v && std::is_enum_v) { + return cmp(enum_name(e1), enum_name(e2)); + } else if constexpr (std::is_enum_v) { + return cmp(enum_name(e1), e2); + } else /* if constexpr (std::is_enum_v) */ { + return cmp(e1, enum_name(e2)); + } + } +}; + +struct raw_access_t {}; + +template +struct FilteredIterator { + Parent parent; + Iterator first; + Iterator last; + Iterator current; + Getter getter; + Predicate predicate; + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = std::remove_reference_t>; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + constexpr FilteredIterator() noexcept = default; + constexpr FilteredIterator(const FilteredIterator&) = default; + constexpr FilteredIterator& operator=(const FilteredIterator&) = default; + constexpr FilteredIterator(FilteredIterator&&) noexcept = default; + constexpr FilteredIterator& operator=(FilteredIterator&&) noexcept = default; + + template && std::is_convertible_v>*> + constexpr explicit FilteredIterator(const FilteredIterator& other) + : parent(other.parent), first(other.first), last(other.last), current(other.current), getter(other.getter), predicate(other.predicate) {} + + constexpr FilteredIterator(Parent p, Iterator begin, Iterator end, Iterator curr, Getter get = {}, Predicate pred = {}) + : parent(p), first(std::move(begin)), last(std::move(end)), current(std::move(curr)), getter{std::move(get)}, predicate{std::move(pred)} { + if (current == first && !predicate(parent, current)) { + ++*this; + } + } + + [[nodiscard]] constexpr reference operator*() const { return getter(parent, current); } + + [[nodiscard]] constexpr pointer operator->() const { return std::addressof(**this); } + + constexpr FilteredIterator& operator++() { + do { + ++current; + } while (current != last && !predicate(parent, current)); + return *this; + } + + [[nodiscard]] constexpr FilteredIterator operator++(int) { + FilteredIterator cp = *this; + ++*this; + return cp; + } + + constexpr FilteredIterator& operator--() { + do { + --current; + } while (current != first && !predicate(parent, current)); + return *this; + } + + [[nodiscard]] constexpr FilteredIterator operator--(int) { + FilteredIterator cp = *this; + --*this; + return cp; + } + + [[nodiscard]] friend constexpr bool operator==(const FilteredIterator& lhs, const FilteredIterator& rhs) { return lhs.current == rhs.current; } + + [[nodiscard]] friend constexpr bool operator!=(const FilteredIterator& lhs, const FilteredIterator& rhs) { return lhs.current != rhs.current; } +}; + +} // namespace detail + +template +using name_less = detail::name_sort_impl; + +template +using name_greater = detail::name_sort_impl>; + +using name_less_case_insensitive = detail::name_sort_impl>>; + +using name_greater_case_insensitive = detail::name_sort_impl>>; + +template +using default_indexing = detail::indexing; + +template > +using comparator_indexing = detail::indexing; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ARRAY // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +struct array { + static_assert(std::is_enum_v); + static_assert(std::is_trivially_constructible_v); + static_assert(enum_count() > 0 && Index::at(enum_values().front())); + + using index_type = Index; + using container_type = std::array()>; + + using value_type = typename container_type::value_type; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + using reference = typename container_type::reference; + using const_reference = typename container_type::const_reference; + using pointer = typename container_type::pointer; + using const_pointer = typename container_type::const_pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using reverse_iterator = typename container_type::reverse_iterator; + using const_reverse_iterator = typename container_type::const_reverse_iterator; + + constexpr reference at(E pos) { + if (auto index = index_type::at(pos)) { + return a[*index]; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::array::at Unrecognized position")); + } + + constexpr const_reference at(E pos) const { + if (auto index = index_type::at(pos)) { + return a[*index]; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::array::at: Unrecognized position")); + } + + [[nodiscard]] constexpr reference operator[](E pos) { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), a[*i]; + } + + [[nodiscard]] constexpr const_reference operator[](E pos) const { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), a[*i]; + } + + [[nodiscard]] constexpr reference front() noexcept { return a.front(); } + + [[nodiscard]] constexpr const_reference front() const noexcept { return a.front(); } + + [[nodiscard]] constexpr reference back() noexcept { return a.back(); } + + [[nodiscard]] constexpr const_reference back() const noexcept { return a.back(); } + + [[nodiscard]] constexpr pointer data() noexcept { return a.data(); } + + [[nodiscard]] constexpr const_pointer data() const noexcept { return a.data(); } + + [[nodiscard]] constexpr iterator begin() noexcept { return a.begin(); } + + [[nodiscard]] constexpr const_iterator begin() const noexcept { return a.begin(); } + + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return a.cbegin(); } + + [[nodiscard]] constexpr iterator end() noexcept { return a.end(); } + + [[nodiscard]] constexpr const_iterator end() const noexcept { return a.end(); } + + [[nodiscard]] constexpr const_iterator cend() const noexcept { return a.cend(); } + + [[nodiscard]] constexpr iterator rbegin() noexcept { return a.rbegin(); } + + [[nodiscard]] constexpr const_iterator rbegin() const noexcept { return a.rbegin(); } + + [[nodiscard]] constexpr const_iterator crbegin() const noexcept { return a.crbegin(); } + + [[nodiscard]] constexpr iterator rend() noexcept { return a.rend(); } + + [[nodiscard]] constexpr const_iterator rend() const noexcept { return a.rend(); } + + [[nodiscard]] constexpr const_iterator crend() const noexcept { return a.crend(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return a.empty(); } + + [[nodiscard]] constexpr size_type size() const noexcept { return a.size(); } + + [[nodiscard]] constexpr size_type max_size() const noexcept { return a.max_size(); } + + constexpr void fill(const V& value) { + for (auto& v : a) { + v = value; + } + } + + constexpr void swap(array& other) noexcept(std::is_nothrow_swappable_v) { + for (std::size_t i = 0; i < a.size(); ++i) { + auto v = std::move(other.a[i]); + other.a[i] = std::move(a[i]); + a[i] = std::move(v); + } + } + + [[nodiscard]] friend constexpr bool operator==(const array& a1, const array& a2) { return detail::equal(a1, a2); } + + [[nodiscard]] friend constexpr bool operator!=(const array& a1, const array& a2) { return !detail::equal(a1, a2); } + + [[nodiscard]] friend constexpr bool operator<(const array& a1, const array& a2) { return detail::lexicographical_compare(a1, a2); } + + [[nodiscard]] friend constexpr bool operator<=(const array& a1, const array& a2) { return !detail::lexicographical_compare(a2, a1); } + + [[nodiscard]] friend constexpr bool operator>(const array& a1, const array& a2) { return detail::lexicographical_compare(a2, a1); } + + [[nodiscard]] friend constexpr bool operator>=(const array& a1, const array& a2) { return !detail::lexicographical_compare(a1, a2); } + + container_type a; +}; + +namespace detail { + +template +constexpr array> to_array_impl(T (&a)[N], std::index_sequence) { + return {{a[I]...}}; +} + +template +constexpr array> to_array_impl(T(&&a)[N], std::index_sequence) { + return {{std::move(a[I])...}}; +} + +} // namespace detail + +template +constexpr std::enable_if_t<(enum_count() == N), array>> to_array(T (&a)[N]) { + return detail::to_array_impl(a, std::make_index_sequence{}); +} + +template +constexpr std::enable_if_t<(enum_count() == N), array>> to_array(T(&&a)[N]) { + return detail::to_array_impl(std::move(a), std::make_index_sequence{}); +} + +template +constexpr std::enable_if_t<(enum_count() == sizeof...(Ts)), array>>> make_array(Ts&&... ts) { + return {{std::forward(ts)...}}; +} + +inline constexpr detail::raw_access_t raw_access{}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// BITSET // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +class bitset { + static_assert(std::is_enum_v); + static_assert(std::is_trivially_constructible_v); + static_assert(enum_count() > 0 && Index::at(enum_values().front())); + + using base_type = std::conditional_t() <= 8, std::uint_least8_t, + std::conditional_t() <= 16, std::uint_least16_t, + std::conditional_t() <= 32, std::uint_least32_t, + std::uint_least64_t>>>; + + static constexpr std::size_t bits_per_base = sizeof(base_type) * 8; + static constexpr std::size_t base_type_count = (enum_count() > 0 ? (enum_count() - 1) / bits_per_base + 1 : 0); + static constexpr std::size_t not_interested = base_type_count * bits_per_base - enum_count(); + static constexpr base_type last_value_max = (base_type{1} << (bits_per_base - not_interested)) - 1; + + template + class reference_impl { + friend class bitset; + + parent_t parent; + std::size_t num_index; + base_type bit_index; + + constexpr reference_impl(parent_t p, std::size_t i) noexcept : reference_impl(p, std::pair{i / bits_per_base, base_type{1} << (i % bits_per_base)}) {} + + constexpr reference_impl(parent_t p, std::pair i) noexcept : parent(p), num_index(std::get<0>(i)), bit_index(std::get<1>(i)) {} + + public: + constexpr reference_impl& operator=(bool v) noexcept { + if (v) { + parent->a[num_index] |= bit_index; + } else { + parent->a[num_index] &= ~bit_index; + } + return *this; + } + + constexpr reference_impl& operator=(const reference_impl& v) noexcept { + if (this == &v) { + return *this; + } + *this = static_cast(v); + return *this; + } + + [[nodiscard]] constexpr operator bool() const noexcept { return (parent->a[num_index] & bit_index) > 0; } + + [[nodiscard]] constexpr bool operator~() const noexcept { return !static_cast(*this); } + + constexpr reference_impl& flip() noexcept { + *this = ~*this; + return *this; + } + }; + + template + [[nodiscard]] constexpr T to_(detail::raw_access_t) const { + T res{}; + T flag{1}; + for (std::size_t i = 0; i < size(); ++i, flag <<= 1) { + if (const_reference{this, i}) { + if (i >= sizeof(T) * 8) { + MAGIC_ENUM_THROW(std::overflow_error("magic_enum::containers::bitset::to: Cannot represent enum in this type")); + } + res |= flag; + } + } + return res; + } + + public: + using index_type = Index; + using container_type = std::array; + using reference = reference_impl<>; + using const_reference = reference_impl; + + constexpr explicit bitset(detail::raw_access_t = raw_access) noexcept : a{{}} {} + + constexpr explicit bitset(detail::raw_access_t, unsigned long long val) : a{{}} { + unsigned long long bit{1}; + for (std::size_t i = 0; i < (sizeof(val) * 8); ++i, bit <<= 1) { + if ((val & bit) > 0) { + if (i >= enum_count()) { + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::constructor: Upper bit set in raw number")); + } + + reference{this, i} = true; + } + } + } + + constexpr explicit bitset(detail::raw_access_t, string_view sv, string_view::size_type pos = 0, string_view::size_type n = string_view::npos, char_type zero = static_cast('0'), char_type one = static_cast('1')) + : a{{}} { + std::size_t i = 0; + for (auto c : sv.substr(pos, n)) { + if (c == one) { + if (i >= enum_count()) { + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::constructor: Upper bit set in raw string")); + } + reference{this, i} = true; + } else if (c != zero) { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized character in raw string")); + } + ++i; + } + } + + constexpr explicit bitset(detail::raw_access_t, const char_type* str, std::size_t n = ~std::size_t{0}, char_type zero = static_cast('0'), char_type one = static_cast('1')) + : bitset(string_view{str, (std::min)(std::char_traits::length(str), n)}, 0, n, zero, one) {} + + constexpr bitset(std::initializer_list starters) : a{{}} { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + for (auto& f : starters) { + *this |= bitset(f); + } + } else { + for (auto& f : starters) { + set(f); + } + } + } + template && magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags, int> = 0> + constexpr explicit bitset(V starter) : a{{}} { + auto u = enum_underlying(starter); + for (E v : enum_values()) { + if (auto ul = enum_underlying(v); (ul & u) != 0) { + u &= ~ul; + (*this)[v] = true; + } + } + if (u != 0) { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in flag")); + } + } + + template > + constexpr explicit bitset(string_view sv, Cmp&& cmp = {}, char_type sep = static_cast('|')) { + for (std::size_t to = 0; (to = magic_enum::detail::find(sv, sep)) != string_view::npos; sv.remove_prefix(to + 1)) { + if (auto v = enum_cast(sv.substr(0, to), cmp)) { + set(*v); + } else { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in string")); + } + } + if (!sv.empty()) { + if (auto v = enum_cast(sv, cmp)) { + set(*v); + } else { + MAGIC_ENUM_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in string")); + } + } + } + + [[nodiscard]] friend constexpr bool operator==(const bitset& lhs, const bitset& rhs) noexcept { return detail::equal(lhs.a, rhs.a); } + + [[nodiscard]] friend constexpr bool operator!=(const bitset& lhs, const bitset& rhs) noexcept { return !detail::equal(lhs.a, rhs.a); } + + [[nodiscard]] constexpr bool operator[](E pos) const { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), static_cast(const_reference(this, *i)); + } + + [[nodiscard]] constexpr reference operator[](E pos) { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i), reference{this, *i}; + } + + constexpr bool test(E pos) const { + if (auto i = index_type::at(pos)) { + return static_cast(const_reference(this, *i)); + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::test: Unrecognized position")); + } + + [[nodiscard]] constexpr bool all() const noexcept { + if constexpr (base_type_count == 0) { + return true; + } + + for (std::size_t i = 0; i < base_type_count - (not_interested > 0); ++i) { + auto check = ~a[i]; + if (check) { + return false; + } + } + + if constexpr (not_interested > 0) { + return a[base_type_count - 1] == last_value_max; + } + } + + [[nodiscard]] constexpr bool any() const noexcept { + for (auto& v : a) { + if (v > 0) { + return true; + } + } + return false; + } + + [[nodiscard]] constexpr bool none() const noexcept { return !any(); } + + [[nodiscard]] constexpr std::size_t count() const noexcept { + std::size_t c = 0; + for (auto& v : a) { + c += detail::popcount(v); + } + return c; + } + + [[nodiscard]] constexpr std::size_t size() const noexcept { return enum_count(); } + + [[nodiscard]] constexpr std::size_t max_size() const noexcept { return enum_count(); } + + constexpr bitset& operator&=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] &= other.a[i]; + } + return *this; + } + + constexpr bitset& operator|=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] |= other.a[i]; + } + return *this; + } + + constexpr bitset& operator^=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] ^= other.a[i]; + } + return *this; + } + + [[nodiscard]] constexpr bitset operator~() const noexcept { + bitset res; + for (std::size_t i = 0; i < base_type_count - (not_interested > 0); ++i) { + res.a[i] = ~a[i]; + } + + if constexpr (not_interested > 0) { + res.a[base_type_count - 1] = ~a[base_type_count - 1] & last_value_max; + } + return res; + } + + constexpr bitset& set() noexcept { + for (std::size_t i = 0; i < base_type_count - (not_interested > 0); ++i) { + a[i] = ~base_type{0}; + } + + if constexpr (not_interested > 0) { + a[base_type_count - 1] = last_value_max; + } + return *this; + } + + constexpr bitset& set(E pos, bool value = true) { + if (auto i = index_type::at(pos)) { + reference{this, *i} = value; + return *this; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::set: Unrecognized position")); + } + + constexpr bitset& reset() noexcept { return *this = bitset{}; } + + constexpr bitset& reset(E pos) { + if (auto i = index_type::at(pos)) { + reference{this, *i} = false; + return *this; + } + MAGIC_ENUM_THROW(std::out_of_range("magic_enum::containers::bitset::reset: Unrecognized position")); + } + + constexpr bitset& flip() noexcept { return *this = ~*this; } + + [[nodiscard]] friend constexpr bitset operator&(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp &= rhs; + return cp; + } + + [[nodiscard]] friend constexpr bitset operator|(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp |= rhs; + return cp; + } + + [[nodiscard]] friend constexpr bitset operator^(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp ^= rhs; + return cp; + } + + template + [[nodiscard]] constexpr explicit operator std::enable_if_t == magic_enum::detail::enum_subtype::flags, E>() const { + E res{}; + for (const auto& e : enum_values()) { + if (test(e)) { + res |= e; + } + } + return res; + } + + [[nodiscard]] string to_string(char_type sep = static_cast('|')) const { + string name; + + for (const auto& e : enum_values()) { + if (test(e)) { + if (!name.empty()) { + name.append(1, sep); + } + auto n = enum_name(e); + name.append(n.data(), n.size()); + } + } + return name; + } + + [[nodiscard]] string to_string(detail::raw_access_t, char_type zero = static_cast('0'), char_type one = static_cast('1')) const { + string name; + name.reserve(size()); + for (std::size_t i = 0; i < size(); ++i) { + name.append(1, const_reference{this, i} ? one : zero); + } + return name; + } + + [[nodiscard]] constexpr unsigned long long to_ullong(detail::raw_access_t raw) const { return to_(raw); } + + [[nodiscard]] constexpr unsigned long long to_ulong(detail::raw_access_t raw) const { return to_(raw); } + + friend std::ostream& operator<<(std::ostream& o, const bitset& bs) { return o << bs.to_string(); } + + friend std::istream& operator>>(std::istream& i, bitset& bs) { + string s; + if (i >> s; !s.empty()) { + bs = bitset(string_view{s}); + } + return i; + } + + private: + container_type a; +}; + +template +explicit bitset(V starter) -> bitset; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SET // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +class set { + using index_type = detail::indexing; + struct Getter { + constexpr const E& operator()(const set*, const E* p) const noexcept { return *p; } + }; + struct Predicate { + constexpr bool operator()(const set* h, const E* e) const noexcept { return h->a[*e]; } + }; + + public: + using container_type = bitset; + using key_type = E; + using value_type = E; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using key_compare = Cmp; + using value_compare = Cmp; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = detail::FilteredIterator; + using const_iterator = detail::FilteredIterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr set() noexcept = default; + + template + constexpr set(InputIt first, InputIt last) { + while (first != last) { + insert(*first++); + } + } + + constexpr set(std::initializer_list ilist) { + for (auto e : ilist) { + insert(e); + } + } + template && magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags, int> = 0> + constexpr explicit set(V starter) { + auto u = enum_underlying(starter); + for (E v : enum_values()) { + if ((enum_underlying(v) & u) != 0) { + insert(v); + } + } + } + + constexpr set(const set&) noexcept = default; + constexpr set(set&&) noexcept = default; + + constexpr set& operator=(const set&) noexcept = default; + constexpr set& operator=(set&&) noexcept = default; + constexpr set& operator=(std::initializer_list ilist) { + for (auto e : ilist) { + insert(e); + } + } + + constexpr const_iterator begin() const noexcept { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::begin()}; + } + + constexpr const_iterator end() const noexcept { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::end()}; + } + + constexpr const_iterator cbegin() const noexcept { return begin(); } + + constexpr const_iterator cend() const noexcept { return end(); } + + constexpr const_reverse_iterator rbegin() const noexcept { return {end()}; } + + constexpr const_reverse_iterator rend() const noexcept { return {begin()}; } + + constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + + constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return s == 0; } + + [[nodiscard]] constexpr size_type size() const noexcept { return s; } + + [[nodiscard]] constexpr size_type max_size() const noexcept { return a.max_size(); } + + constexpr void clear() noexcept { + a.reset(); + s = 0; + } + + constexpr std::pair insert(const value_type& value) noexcept { + if (auto i = index_type::at(value)) { + typename container_type::reference ref = a[value]; + bool r = !ref; + if (r) { + ref = true; + ++s; + } + + return {iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}, r}; + } + return {end(), false}; + } + + constexpr std::pair insert(value_type&& value) noexcept { return insert(value); } + + constexpr iterator insert(const_iterator, const value_type& value) noexcept { return insert(value).first; } + + constexpr iterator insert(const_iterator hint, value_type&& value) noexcept { return insert(hint, value); } + + template + constexpr void insert(InputIt first, InputIt last) noexcept { + while (first != last) { + insert(*first++); + } + } + + constexpr void insert(std::initializer_list ilist) noexcept { + for (auto v : ilist) { + insert(v); + } + } + + template + constexpr std::pair emplace(Args&&... args) noexcept { + return insert({std::forward(args)...}); + } + + template + constexpr iterator emplace_hint(const_iterator, Args&&... args) noexcept { + return emplace(std::forward(args)...).first; + } + + constexpr iterator erase(const_iterator pos) noexcept { + erase(*pos++); + return pos; + } + + constexpr iterator erase(const_iterator first, const_iterator last) noexcept { + while ((first = erase(first)) != last) { + } + return first; + } + + constexpr size_type erase(const key_type& key) noexcept { + typename container_type::reference ref = a[key]; + bool res = ref; + if (res) { + --s; + } + ref = false; + return res; + } + + template + constexpr std::enable_if_t, size_type> erase(K&& x) noexcept { + size_type c = 0; + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last;) { + c += erase(*first++); + } + return c; + } + + void swap(set& other) noexcept { + set cp = *this; + *this = other; + other = cp; + } + + [[nodiscard]] constexpr size_type count(const key_type& key) const noexcept { return index_type::at(key) && a[key]; } + + template + [[nodiscard]] constexpr std::enable_if_t, size_type> count(const K& x) const { + size_type c = 0; + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last; ++first) { + c += count(*first); + } + return c; + } + + [[nodiscard]] constexpr const_iterator find(const key_type& key) const noexcept { + if (auto i = index_type::at(key); i && a.test(key)) { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}; + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> find(const K& x) const { + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last; ++first) { + if (a.test(*first)) { + return find(*first); + } + } + return end(); + } + + [[nodiscard]] constexpr bool contains(const key_type& key) const noexcept { return count(key); } + + template + [[nodiscard]] constexpr std::enable_if_t, bool> contains(const K& x) const noexcept { + return count(x) > 0; + } + + [[nodiscard]] constexpr std::pair equal_range(const key_type& key) const noexcept { return {lower_bound(key), upper_bound(key)}; } + + template + [[nodiscard]] constexpr std::enable_if_t, std::pair> equal_range(const K& x) const noexcept { + return {lower_bound(x), upper_bound(x)}; + } + + [[nodiscard]] constexpr const_iterator lower_bound(const key_type& key) const noexcept { + if (auto i = index_type::at(key)) { + auto it = const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}; + return a.test(key) ? it : std::next(it); + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> lower_bound(const K& x) const noexcept { + auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); + return first != last ? lower_bound(*first) : end(); + } + + [[nodiscard]] constexpr const_iterator upper_bound(const key_type& key) const noexcept { + if (auto i = index_type::at(key)) { + return std::next(const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}); + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> upper_bound(const K& x) const noexcept { + auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); + return first != last ? upper_bound(*std::prev(last)) : end(); + } + + [[nodiscard]] constexpr key_compare key_comp() const { return {}; } + + [[nodiscard]] constexpr value_compare value_comp() const { return {}; } + + [[nodiscard]] constexpr friend bool operator==(const set& lhs, const set& rhs) noexcept { return lhs.a == rhs.a; } + + [[nodiscard]] constexpr friend bool operator!=(const set& lhs, const set& rhs) noexcept { return lhs.a != rhs.a; } + + [[nodiscard]] constexpr friend bool operator<(const set& lhs, const set& rhs) noexcept { + if (lhs.s < rhs.s) { + return true; + } + if (rhs.s < lhs.s) { + return false; + } + + for (auto it = index_type::begin(); it != index_type::end(); ++it) { + if (auto c = rhs.contains(*it); c != lhs.contains(*it)) { + return c; + } + } + return false; + } + + [[nodiscard]] constexpr friend bool operator<=(const set& lhs, const set& rhs) noexcept { return !(rhs < lhs); } + + [[nodiscard]] constexpr friend bool operator>(const set& lhs, const set& rhs) noexcept { return rhs < lhs; } + + [[nodiscard]] constexpr friend bool operator>=(const set& lhs, const set& rhs) noexcept { return !(lhs < rhs); } + + template + size_type erase_if(Pred pred) { + auto old_size = size(); + for (auto i = begin(), last = end(); i != last;) { + if (pred(*i)) { + i = erase(i); + } else { + ++i; + } + } + return old_size - size(); + } + + private: + container_type a; + std::size_t s = 0; +}; + +template +explicit set(V starter) -> set; + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), V&> get(array& a) noexcept { + return a.a[I]; +} + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), V&&> get(array&& a) noexcept { + return std::move(a.a[I]); +} + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), const V&> get(const array& a) noexcept { + return a.a[I]; +} + +template +constexpr std::enable_if_t<(std::is_integral_v && I < enum_count()), const V&&> get(const array&& a) noexcept { + return std::move(a.a[I]); +} + +template +constexpr std::enable_if_t && enum_contains(Enum), V&> get(array& a) { + return a[Enum]; +} + +template +constexpr std::enable_if_t && enum_contains(Enum), V&&> get(array&& a) { + return std::move(a[Enum]); +} + +template +constexpr std::enable_if_t && enum_contains(Enum), const V&> get(const array& a) { + return a[Enum]; +} + +template +constexpr std::enable_if_t && enum_contains(Enum), const V&&> get(const array&& a) { + return std::move(a[Enum]); +} + +} // namespace magic_enum::containers + +#endif // NEARGYE_MAGIC_ENUM_CONTAINERS_HPP diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_flags.hpp b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_flags.hpp new file mode 100644 index 0000000..f2d81b5 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_flags.hpp @@ -0,0 +1,222 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_FLAGS_HPP +#define NEARGYE_MAGIC_ENUM_FLAGS_HPP + +#include "magic_enum.hpp" + +#if defined(__clang__) +# pragma clang diagnostic push +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. +#elif defined(_MSC_VER) +# pragma warning(push) +#endif + +namespace magic_enum { + +namespace detail { + +template > +constexpr U values_ors() noexcept { + static_assert(S == enum_subtype::flags, "magic_enum::detail::values_ors requires valid subtype."); + + auto ors = U{0}; + for (std::size_t i = 0; i < count_v; ++i) { + ors |= static_cast(values_v[i]); + } + + return ors; +} + +} // namespace magic_enum::detail + +// Returns name from enum-flags value. +// If enum-flags value does not have name or value out of range, returns empty string. +template +[[nodiscard]] auto enum_flags_name(E value, char_type sep = static_cast('|')) -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + string name; + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(enum_value(i)); (static_cast(value) & v) != 0) { + if (const auto n = detail::names_v[i]; !n.empty()) { + check_value |= v; + if (!name.empty()) { + name.append(1, sep); + } + name.append(n.data(), n.size()); + } else { + return {}; // Value out of range. + } + } + } + + if (check_value != 0 && check_value == static_cast(value)) { + return name; + } + return {}; // Invalid value or out of range. +} + +// Obtains enum-flags value from integer value. +// Returns optional with enum-flags value. +template +[[nodiscard]] constexpr auto enum_flags_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + using U = underlying_type_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::count_v == 0) { + static_cast(value); + return {}; // Empty enum. + } else { + if constexpr (detail::is_sparse_v) { + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(enum_value(i)); (value & v) != 0) { + check_value |= v; + } + } + + if (check_value != 0 && check_value == value) { + return static_cast(value); + } + } else { + constexpr auto min = detail::min_v; + constexpr auto max = detail::values_ors(); + + if (value >= min && value <= max) { + return static_cast(value); + } + } + return {}; // Invalid value or out of range. + } +} + +// Obtains enum-flags value from name. +// Returns optional with enum-flags value. +template > +[[nodiscard]] constexpr auto enum_flags_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { + using D = std::decay_t; + using U = underlying_type_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::count_v == 0) { + static_cast(value); + return {}; // Empty enum. + } else { + auto result = U{0}; + while (!value.empty()) { + const auto d = detail::find(value, '|'); + const auto s = (d == string_view::npos) ? value : value.substr(0, d); + auto f = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(s, detail::names_v[i], p)) { + f = static_cast(enum_value(i)); + result |= f; + break; + } + } + if (f == U{0}) { + return {}; // Invalid value or out of range. + } + value.remove_prefix((d == string_view::npos) ? value.size() : d + 1); + } + + if (result != U{0}) { + return static_cast(result); + } + return {}; // Invalid value or out of range. + } +} + +// Checks whether enum-flags contains value with such value. +template +[[nodiscard]] constexpr auto enum_flags_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_flags_cast(static_cast(value))); +} + +// Checks whether enum-flags contains value with such integer value. +template +[[nodiscard]] constexpr auto enum_flags_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_flags_cast(value)); +} + +// Checks whether enum-flags contains enumerator with such name. +template > +[[nodiscard]] constexpr auto enum_flags_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_flags_cast(value, std::move(p))); +} + +// Checks whether `flags set` contains `flag`. +// Note: If `flag` equals 0, it returns false, as 0 is not a flag. +template +constexpr auto enum_flags_test(E flags, E flag) noexcept -> detail::enable_if_t { + using U = underlying_type_t; + + return static_cast(flag) && ((static_cast(flags) & static_cast(flag)) == static_cast(flag)); +} + +// Checks whether `lhs flags set` and `rhs flags set` have common flags. +// Note: If `lhs flags set` or `rhs flags set` equals 0, it returns false, as 0 is not a flag, and therfore cannot have any matching flag. +template +constexpr auto enum_flags_test_any(E lhs, E rhs) noexcept -> detail::enable_if_t { + using U = underlying_type_t; + + return (static_cast(lhs) & static_cast(rhs)) != 0; +} + +} // namespace magic_enum + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // NEARGYE_MAGIC_ENUM_FLAGS_HPP diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_format.hpp b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_format.hpp new file mode 100644 index 0000000..f10371f --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_format.hpp @@ -0,0 +1,114 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_FORMAT_HPP +#define NEARGYE_MAGIC_ENUM_FORMAT_HPP + +#include "magic_enum.hpp" +#include "magic_enum_flags.hpp" + +#if !defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT) +# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT 1 +# define MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE +#endif + +namespace magic_enum::customize { + // customize enum to enable/disable automatic std::format + template + constexpr bool enum_format_enabled() noexcept { + return MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT; + } +} // magic_enum::customize + +#if defined(__cpp_lib_format) + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#include +#endif + +template +struct std::formatter> && magic_enum::customize::enum_format_enabled(), char>> : std::formatter { + template + auto format(E e, FormatContext& ctx) const { + static_assert(std::is_same_v, "formatter requires string_view::value_type type same as char."); + using D = std::decay_t; + + if constexpr (magic_enum::detail::supported::value) { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + if (const auto name = magic_enum::enum_flags_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } else { + if (const auto name = magic_enum::enum_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } + } + return formatter::format(std::to_string(magic_enum::enum_integer(e)), ctx); + } +}; + +#endif + +#if defined(FMT_VERSION) + +#include +struct fmt::formatter> && magic_enum::customize::enum_format_enabled(), char>> : fmt::formatter { + template + auto format(E e, FormatContext& ctx) const { + static_assert(std::is_same_v, "formatter requires string_view::value_type type same as char."); + using D = std::decay_t; + + if constexpr (magic_enum::detail::supported::value) { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + if (const auto name = magic_enum::enum_flags_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } else { + if (const auto name = magic_enum::enum_name(e); !name.empty()) { + return formatter::format(std::string_view{name.data(), name.size()}, ctx); + } + } + } + return formatter::format(std::to_string(magic_enum::enum_integer(e)), ctx); + } +}; + +#endif + +#if defined(MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE) +# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT +# undef MAGIC_ENUM_DEFAULT_ENABLE_ENUM_FORMAT_AUTO_DEFINE +#endif + +#endif // NEARGYE_MAGIC_ENUM_FORMAT_HPP diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_fuse.hpp b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_fuse.hpp new file mode 100644 index 0000000..6b2a3ef --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_fuse.hpp @@ -0,0 +1,89 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_FUSE_HPP +#define NEARGYE_MAGIC_ENUM_FUSE_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +template +constexpr optional fuse_one_enum(optional hash, E value) noexcept { + if (hash) { + if (const auto index = enum_index(value)) { + return (*hash << log2((enum_count() << 1) - 1)) | *index; + } + } + return {}; +} + +template +constexpr optional fuse_enum(E value) noexcept { + return fuse_one_enum(0, value); +} + +template +constexpr optional fuse_enum(E head, Es... tail) noexcept { + return fuse_one_enum(fuse_enum(tail...), head); +} + +template +constexpr auto typesafe_fuse_enum(Es... values) noexcept { + enum class enum_fuse_t : std::uintmax_t; + const auto fuse = fuse_enum(values...); + if (fuse) { + return optional{static_cast(*fuse)}; + } + return optional{}; +} + +} // namespace magic_enum::detail + +// Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements. +template +[[nodiscard]] constexpr auto enum_fuse(Es... values) noexcept { + static_assert((std::is_enum_v> && ...), "magic_enum::enum_fuse requires enum type."); + static_assert(sizeof...(Es) >= 2, "magic_enum::enum_fuse requires at least 2 values."); + static_assert((detail::log2(enum_count>() + 1) + ...) <= (sizeof(std::uintmax_t) * 8), "magic_enum::enum_fuse does not work for large enums"); +#if defined(MAGIC_ENUM_NO_TYPESAFE_ENUM_FUSE) + const auto fuse = detail::fuse_enum...>(values...); +#else + const auto fuse = detail::typesafe_fuse_enum...>(values...); +#endif + return MAGIC_ENUM_ASSERT(fuse), fuse; +} + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_FUSE_HPP diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_iostream.hpp b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_iostream.hpp new file mode 100644 index 0000000..6130dcb --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_iostream.hpp @@ -0,0 +1,117 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_IOSTREAM_HPP +#define NEARGYE_MAGIC_ENUM_IOSTREAM_HPP + +#include "magic_enum.hpp" +#include "magic_enum_flags.hpp" + +#ifndef MAGIC_ENUM_USE_STD_MODULE +#include +#endif + +namespace magic_enum { + +namespace ostream_operators { + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, E value) { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::supported::value) { + if constexpr (detail::subtype_v == detail::enum_subtype::flags) { + if (const auto name = enum_flags_name(value); !name.empty()) { + for (const auto c : name) { + os.put(c); + } + return os; + } + } else { + if (const auto name = enum_name(value); !name.empty()) { + for (const auto c : name) { + os.put(c); + } + return os; + } + } + } + return (os << static_cast(value)); +} + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { + return value ? (os << *value) : os; +} + +} // namespace magic_enum::ostream_operators + +namespace istream_operators { + +template = 0> +std::basic_istream& operator>>(std::basic_istream& is, E& value) { + using D = std::decay_t; + + std::basic_string s; + is >> s; + if constexpr (detail::supported::value) { + if constexpr (detail::subtype_v == detail::enum_subtype::flags) { + if (const auto v = enum_flags_cast(s)) { + value = *v; + } else { + is.setstate(std::basic_ios::failbit); + } + } else { + if (const auto v = enum_cast(s)) { + value = *v; + } else { + is.setstate(std::basic_ios::failbit); + } + } + } else { + is.setstate(std::basic_ios::failbit); + } + return is; +} + +} // namespace magic_enum::istream_operators + +namespace iostream_operators { + +using magic_enum::ostream_operators::operator<<; +using magic_enum::istream_operators::operator>>; + +} // namespace magic_enum::iostream_operators + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_IOSTREAM_HPP diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_switch.hpp b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_switch.hpp new file mode 100644 index 0000000..63fc19a --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_switch.hpp @@ -0,0 +1,195 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_SWITCH_HPP +#define NEARGYE_MAGIC_ENUM_SWITCH_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +struct default_result_type {}; + +template +struct identity { + using type = T; +}; + +struct nonesuch {}; + +template > +struct invoke_result : identity {}; + +template +struct invoke_result : std::invoke_result {}; + +template +using invoke_result_t = typename invoke_result::type; + +template +constexpr auto common_invocable(std::index_sequence) noexcept { + static_assert(std::is_enum_v, "magic_enum::detail::invocable_index requires enum type."); + + if constexpr (count_v == 0) { + return identity{}; + } else { + return std::common_type[I]>>...>{}; + } +} + +template +constexpr auto result_type() noexcept { + static_assert(std::is_enum_v, "magic_enum::detail::result_type requires enum type."); + + constexpr auto seq = std::make_index_sequence>{}; + using R = typename decltype(common_invocable(seq))::type; + if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { + return identity{}; + } else { + return identity{}; + } + } else { + if constexpr (std::is_convertible_v) { + return identity{}; + } else if constexpr (std::is_convertible_v) { + return identity{}; + } else { + return identity{}; + } + } +} + +template , typename R = typename decltype(result_type())::type> +using result_t = std::enable_if_t && !std::is_same_v, R>; + +#if !defined(MAGIC_ENUM_ENABLE_HASH) && !defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr decltype(auto) constexpr_switch_impl(F&& f, E value, Def&& def) { + if constexpr(I < End) { + constexpr auto v = enum_constant()>{}; + if (value == v) { + if constexpr (std::is_invocable_r_v) { + return static_cast(std::forward(f)(v)); + } else { + return def(); + } + } else { + return constexpr_switch_impl(std::forward(f), value, std::forward(def)); + } + } else { + return def(); + } +} + +template +constexpr decltype(auto) constexpr_switch(F&& f, E value, Def&& def) { + static_assert(is_enum_v, "magic_enum::detail::constexpr_switch requires enum type."); + + if constexpr (count_v == 0) { + return def(); + } else { + return constexpr_switch_impl<0, count_v, R, E, S>(std::forward(f), value, std::forward(def)); + } +} +#endif + +} // namespace magic_enum::detail + +template , typename F, typename R = detail::result_t> +constexpr decltype(auto) enum_switch(F&& f, E value) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_switch requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + std::forward(f), + value, + detail::default_result_type_lambda); +#else + return detail::constexpr_switch( + std::forward(f), + value, + detail::default_result_type_lambda); +#endif +} + +template > +constexpr decltype(auto) enum_switch(F&& f, E value) { + return enum_switch(std::forward(f), value); +} + +template , typename F, typename R = detail::result_t> +constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_switch requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + std::forward(f), + value, + [&result]() -> R { return std::forward(result); }); +#else + return detail::constexpr_switch( + std::forward(f), + value, + [&result]() -> R { return std::forward(result); }); +#endif +} + +template > +constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) { + return enum_switch(std::forward(f), value, std::forward(result)); +} + +} // namespace magic_enum + +template <> +struct std::common_type : magic_enum::detail::identity {}; + +template +struct std::common_type : magic_enum::detail::identity {}; + +template +struct std::common_type : magic_enum::detail::identity {}; + +#endif // NEARGYE_MAGIC_ENUM_SWITCH_HPP diff --git a/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_utility.hpp b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_utility.hpp new file mode 100644 index 0000000..5368b02 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/hulc/magic_enum/magic_enum/magic_enum_utility.hpp @@ -0,0 +1,138 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.7 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef NEARGYE_MAGIC_ENUM_UTILITY_HPP +#define NEARGYE_MAGIC_ENUM_UTILITY_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +template +constexpr auto for_each(F&& f, std::index_sequence) { + constexpr bool has_void_return = (std::is_void_v[I]>>> || ...); + constexpr bool all_same_return = (std::is_same_v[0]>>, std::invoke_result_t[I]>>> && ...); + + if constexpr (has_void_return) { + (f(enum_constant[I]>{}), ...); + } else if constexpr (all_same_return) { + return std::array{f(enum_constant[I]>{})...}; + } else { + return std::tuple{f(enum_constant[I]>{})...}; + } +} + +template +constexpr bool all_invocable(std::index_sequence) { + if constexpr (count_v == 0) { + return false; + } else { + return (std::is_invocable_v[I]>> && ...); + } +} + +} // namespace magic_enum::detail + +template , typename F, detail::enable_if_t = 0> +constexpr auto enum_for_each(F&& f) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_for_each requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + constexpr auto sep = std::make_index_sequence>{}; + + if constexpr (detail::all_invocable(sep)) { + return detail::for_each(std::forward(f), sep); + } else { + static_assert(detail::always_false_v, "magic_enum::enum_for_each requires invocable of all enum value."); + } +} + +template > +[[nodiscard]] constexpr auto enum_next_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = (static_cast(*i) + n); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return {}; +} + +template > +[[nodiscard]] constexpr auto enum_next_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = ((((static_cast(*i) + n) % count) + count) % count); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return MAGIC_ENUM_ASSERT(false), value; +} + +template > +[[nodiscard]] constexpr auto enum_prev_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = (static_cast(*i) - n); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return {}; +} + +template > +[[nodiscard]] constexpr auto enum_prev_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const std::ptrdiff_t index = ((((static_cast(*i) - n) % count) + count) % count); + if (index >= 0 && index < count) { + return enum_value(static_cast(index)); + } + } + return MAGIC_ENUM_ASSERT(false), value; +} + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_UTILITY_HPP diff --git a/module/dll_source_lzy/ecap/lib/makelib_common.h b/module/dll_source_lzy/ecap/lib/makelib_common.h new file mode 100644 index 0000000..7205625 --- /dev/null +++ b/module/dll_source_lzy/ecap/lib/makelib_common.h @@ -0,0 +1,14 @@ +#pragma once +#define export 1 +#if defined(_MSC_VER) || defined(WIN64) || defined(WIN32) || defined(__NT__) +#define ex_export __declspec(dllexport) +#define ex_emport __declspec(dllimport) +#else // linux or mac +#define ex_export __attribute__((visibility("default"))) +#define ex_emport __attribute__((visibility("default"))) +#endif +#if defined(export) +#define ex_declare ex_export +#else +#define ex_declare ex_emport +#endif \ No newline at end of file diff --git a/module/dll_source_lzy/ecap/lib9002.so b/module/dll_source_lzy/ecap/lib9002.so new file mode 100644 index 0000000000000000000000000000000000000000..b02d4f96519d45da180d8e9a50bb1f8a457832d9 GIT binary patch literal 361136 zcmeFa3s_af`UkvFDKsxwX;xZTSY)YtBPf&>2q@ISb4(Kl0VVG zj+R|4>}nTFyIN^sW!lBU%Cd`znYlP*-m>hb-|wB7wRjo#KBuVfd!Fxk&Z_0wzu&y; zop;`ud1uzlV(%MM(uTGQ32AMpqqT9R5rWV4yCspJ_%+vss!xWIV8j`x;ok`3d?DM$ zPC5C*mUWV|u12`Tl!wq*E3u(n=TC1X609q~PvqxNCTrI-ZzRozVO{xsr+_!tNo&n5 z&9BEgiQ9~tMe=IhM^?~%wXaC|+IO2@6Qr4jbq!OulN6n+RJwkRP;%B)zi*4WPrpX0 z`>ZSFK}W`3e@;vCOIP<@mLfIj*W~t6zjd|p8-x4EFMsq<)ozSc`30+oZl_9()>YP~ z^d;$_HZyG0I0NpfC0ne9OP|kFn9`j)UT(j&(>KSnh8u_19{J+UkGhQCjb!6gD0~vv z2%M+kJhP8+Q~K#`lCnBSk4ZN2{~4BaQ|RtqMn%td=^q-4BZeA1irbgBiJXvpO4Q0$ zQzFBRtlkM>VYh{3c{YZ={Z`ws&h2W8()SS7H$Nq8es$OGiEl@Yx~t-*?&bc;SKZ#K{jdwiE)9(wTVCxwV`F((dsleth87CdD;9QTh7Uvr{x8U50 z^L?Dw@c};6;oOGvBb*=Ow2tlg@F~vEaDI;S3!FP~euZ-vPO6+eIO+Hn=RTY!&IX+O zaUQ^V5a%JB)^P+Meo){ju0P`ZNnz`93?F{M`5VsTI8Wegg*Fh1lMZSRbhO2_o%(#5 zy0*u)BhF4Z!*O=T*###ZUDdUlx}J^eIqI{NFpTcNJ#e0{_y~3FiR%S8FT!~-&M2Ih z;Ovd_Qk>S&M}2nS>QtYjaqW+D0M3Cp<8fY&^9r1Ua1O?KCC(u@Q*aK&nTnGi!xc_b z*O9o6!Z{jeCeCp<$K$*P=R};7a9)cu3uiV?>zIzuxj20|XW*QP(>n6lXX5gKXW=YX zd1)tx>`7X|_I6uHi$49t+jB`8APjG&Ua|g~ZaMH0;UFq{z_`FNudR+J5{6^t# zaowjro9fz#>-RVh;5>-)FwP@5={Sn(PdJY${0pwX;`|-wamBS(<4Y*6VL032JPl`i zoYoPy|LQxhzH;Zd7pLxhZf5j*L!N%|mDTfB-#s(o>B>t^+j7;hMOXcD+2?mnx$n@a z>8-9C({c7UN1l2$e#=)opMB|)`#U^+_b(H=ROUW%?0RR{_A6F?(WB^+l`@>hCY}x(7=JEY!ACCO$rQR1$?>Okjn+t2&e7}9xZ=u5; z9o%(K$!S|Ih<$l+|BBXkK4Ly_??v1D9hx{dAtHCt6PwJK$Sb?HcAPyWA+P$iUc;|@ z^_E9#62~Rp`NiaswZ9LXSmW9C*5$`9agEr2c-Y{V4kTvoSo2WAYnv|k_QcE2e|z}J z;ced<@%4!dH{bc&%yU2cd0VPKd|uyo|MA+(dv3oW;Dka^>{uXdgiJ9u^X8E+gfTmRARb6&Z7?t^c=fB(PMpZdsI z?<_7}7IROZFUD?J^VON_e}8oE)gQfg?!S+xuSz@nz>6P0v+o&S$Z31W#$5W`uHmnq z-}Tm|{>R%cyX*ae(OojveLFnv%Ux-Aopb(#OTEfR-L| zlo76f#!dR_uF#pTkoUe`pFhS~_0FmjpWi<9(Gy=S>N~Oa(z9lzUiCw#q`U53{K5rB zWyr9jy*^pne%GD;8;0z<=eMhGJaa{IO~Q~#{tI4n>^CiRff;WG?$Fct7XF5du`*_v;QPIE7>zr}w3$2U?KdE2!^Ms{uZp&
wLjc^;R9zCw!it!`CpI7 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 literal 0 HcmV?d00001 diff --git a/module/get_track_tool_main_backup.cpp b/module/get_track_tool_main_backup.cpp new file mode 100644 index 0000000..28fb013 --- /dev/null +++ b/module/get_track_tool_main_backup.cpp @@ -0,0 +1,277 @@ +#include +#include +#include +#include "Local_Server/Data_Source.h" +#include "Local_Server/Data_Source.h" +#include "SSR/export.h" +using namespace Psc; +int max_distance = 700; +class MY_File_Data_Source : public File_Data_Source { +public: + BaseLogger* logger{}; + void pre_handle(const std::string& data) override {} + void handle_mode_s(std::string& packet) override {} +}; +std::map air_map; +static size_t line_num = 0; +static size_t line_num_ex = 0; +namespace fs = std::filesystem; +// "C:/wyc/CLionProjects/plantSunCat/mainProjects/Radarcape/data/pos/110.txt" +void handle(const fs::path& file_path, const fs::path& rel_path) { + line_num = 0; + line_num_ex = 0; + MY_File_Data_Source fs; + auto u8_string = rel_path.u8string(); + std::string t(u8_string.begin(), u8_string.end()); + auto path = get_exe_dir() + "/tarck_logs/" + t; + auto ttt = file_path.u8string(); + std::string fp(ttt.begin(), ttt.end()); + auto key = fp; + std::replace(key.begin(), key.end(), '/', '_'); + std::replace(key.begin(), key.end(), '\\', '_'); + fs.logger = new BaseLogger(path, 100 * 100 * 10000, 1); + fs.key = key; + fs.data_type = Mock_Data_Type::BIN_Blank_One_Line_With_Escape; + fs.file_path = fp; + fs.play_mode = Play_Mode::analysis; + fs.open(); + std::string buffer; + auto mode_data = fs.read(); + while (!mode_data.empty()) { + line_num_ex++; + Binary_Format_handle_buffer(buffer, mode_data, [&](std::string& packet) { + line_num++; + if (packet[0] != 0x1a) { + std::cout << "first:" << mem2hex(packet) << std::endl; + return; + } + std::string error_len = key + " " + LOG_POS_SIMPLE + " mode_s_error_length"; + auto type = packet[1]; + if (type == Msg::AC) { + if (packet.size() != Msg::AC_len) { + fs.logger->c_debug(error_len, {}, + std::to_string(type) + " size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet) + " should:" + std::to_string(Msg::AC_len)); + return; + } + } else if (type == Msg::S7) { + if (packet.size() != Mode_S_Msg::S7_len) { + fs.logger->c_debug(error_len, {}, + std::to_string(type) + " size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet) + " should:" + std::to_string(Msg::S7_len)); + return; + } + } else if (type == Msg::S14) { + if (packet.size() != Msg::S14_len) { + fs.logger->c_debug(error_len, {}, + std::to_string(type) + " size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet) + " should:" + std::to_string(Msg::S14_len)); + return; + } + } else if (type == Msg::Radarcape_status) { + if (packet.size() != Msg::Radarcape_status_len) { + fs.logger->c_debug(error_len, {}, + std::to_string(type) + " size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet) + " should:" + std::to_string(Msg::Radarcape_status_len)); + return; + } + } else if (type == Msg::HULC_Status) { + if (packet.size() != Msg::HULC_len) { + fs.logger->c_debug(error_len, {}, + std::to_string(type) + " HULC size:" + std::to_string(packet.size()) + " hex:" + + mem2hex(packet)); + return; + } + } else { + // std::cout << "unknown_type:" << mem2hex(packet); + rotating_log("unknown_type", mem2hex(packet)); + return; + } + bool mode_s = type == Msg::S7 || type == Msg::S14; + if (!mode_s) {} + if (type == Msg::AC) {} else if (type == Msg::HULC_Status) {} else if (type == Msg::Radarcape_status) { + auto msg = create_Radarcape_STATUS_Message(packet); + std::cout << msg.toJson().to_json_string() << std::endl; + fs.logger->debug("Radarcape_status/radarcape", {}, msg.toJson().to_json_string()); + } else if (mode_s) { + // 拓展点 + auto mode_s_msg = std::make_unique(nullptr, packet); + std::string& msg_hex = mode_s_msg->msg_hex; + std::string msg_bin = hex2bin(mode_s_msg->msg_hex); + auto df = ::decode_df(msg_bin); + std::string icao = ::decode_icao(msg_bin); + JSON msg_json = JSON::object(); + MLAT_timestamp* mlat_timestamp = &mode_s_msg->mlat_timestamp; + Log_Type lt({}, { + {"行号", std::to_string(line_num)}, + {"ICAO", icao}, + //{"DF", std::to_string(static_cast(df))}, + {"signal_level", std::to_string((int)mode_s_msg->signal_level)}, + {"时间戳", mode_s_msg->mlat_timestamp.to_string()}, + //{"msg_hex", msg_hex}, + {"msg_hex_blank", mem2hex(hex2mem(msg_hex), true, " ")} + }); + lt.new_line = true; + //fs.logger->debug("收到报文", lt, ""); + if (mode_s_msg->df == Downlink_Format::Extended_Squitter_17) { + if (!with_PI::check_crc(msg_bin, icao)) { + fs.logger->error("crc_error", lt.append("pos", LOG_POS_SIMPLE), ""); + return; + } + int tc = type_code(msg_bin); + auto& icao = mode_s_msg->icao; + if (air_map.find(icao) == air_map.end()) { + auto i = new Aircraft_Info(icao); + air_map[icao] = i; + } + auto& info = *air_map[mode_s_msg->icao]; + // if (tc >= 5 && tc <= 8) { + // auto& tt = info.BDS06_surface_position.value; + // if (!tt.has_value()) { + // tt = typename std::remove_reference_t::value_type(); + // } + // auto& t = tt.value(); + // info.refresh_pos(true); + // t.base_station_pos = CPR::Position{37, 121}; + // t.parse(msg_bin); + // auto p = t.parse_message(msg_bin, static_cast(mlat_timestamp->daysec), max_distance); + // if (p.has_value()) { + // info.surface_pos_track_list.push(p.value()); + // std::cout << "第一次出现地面轨迹 手动崩溃!" << std::endl; + // std::exit(0); + // fs.logger->debug("surface/地面轨迹", lt, info.surface_pos_track_list.to_string()); + // } + // msg_json.append_list(t.toJson().children); + // } else + if ((tc >= 9 && tc <= 18) || (tc >= 20 && tc <= 22)) { + auto& tt = info.BDS05_airborne_position.value; + if (!tt.has_value()) { + tt = typename std::remove_reference_t::value_type(); + } + auto& t = tt.value(); + t.parse(msg_bin); + info.refresh_pos(true); + auto p = t.parse_message(msg_bin, static_cast(mlat_timestamp->daysec), max_distance); + if (p.has_value()) { + double meter = t.get_alt_meter(); + double x, y, z; + CPR::WGS84_LBH_to_XYZ(p->lon, p->lat, meter, x, y, z); + std::ostringstream oss; + oss << std::fixed << std::setprecision(10); + oss << std::endl; + oss << "经度-纬度-高度(m)" << "[" << p->lon << ", " << p->lat << ", " << meter << "]" << + std::endl; + oss << "笛卡尔坐标系:" << "[" << x << ", " << y << ", " << z << "]" << " Altitude_type:" << + to_string(t.get_altitude_type()) << std::endl; + fs.logger->debug("坐标解析成功", lt, oss.str()); + Position_3D p2(p->lat, p->lon, 0); + info.air_pos_track_list.push(p2); + } else { + fs.logger->debug("坐标解析失败", lt, "\n"); + } + msg_json.append_list(t.toJson().children); + } else { + lt.append("tc", std::to_string(tc)); + fs.logger->debug("未知的tc", lt, "\n"); + } + } + } + }); + mode_data = fs.read(); + } + std::cout << "line_num: " << line_num << std::endl; + std::cout << "line_num_ex: " << line_num_ex << std::endl; + fs.close(); +} +// void list_directory(const fs::path& dir_path, const std::string& relative_path = "") { +// for (const auto& entry : fs::directory_iterator(dir_path)) { +// std::string file_path = entry.path().string(); +// std::string filename = entry.path().filename().string(); +// std::string full_relative_path = relative_path.empty() ? filename : relative_path + "/" + filename; +// if (fs::is_directory(entry.status())) { +// list_directory(dir_path, full_relative_path); // 递归遍历子目录 +// } else { +// handle(file_path, full_relative_path + filename); // 对文件进行处理 +// } +// } +// } +void list_directory(const fs::path& dir_path, const fs::path& base_path) { + for (const auto& entry : fs::directory_iterator(dir_path)) { + fs::path file_path = entry.path(); + auto relative_path = fs::relative(file_path, base_path); // 计算相对路径 + if (fs::is_directory(entry.status())) { + list_directory(file_path, base_path); // 递归遍历子目录 + } else { + std::cout << "Full path: " << (const char*)file_path.u8string().c_str() << std::endl; // 输出完整路径 + std::cout << "Relative path: " << (const char*)relative_path.u8string().c_str() << std::endl; // 输出相对路径 + handle(file_path, relative_path); + } + } +} +std::wstring _transformString2Wstring(const std::string& s) { + setlocale(LC_CTYPE, "en_US.UTF-8"); + const char* _Source = s.c_str(); + size_t len = strlen(_Source) + 1; + size_t converted = 0; + wchar_t* wStr = new wchar_t[len]; + mbstowcs_s(&converted, wStr, len, _Source, _TRUNCATE); + std::wstring result(wStr); + delete[] wStr; + return result; +} +std::wstring gbk_to_wstring(const std::string& gbk) { + int len = MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, nullptr, 0); + std::wstring wstr(len, L'\0'); + MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), -1, &wstr[0], len); + // 去掉末尾'\0' + if (!wstr.empty() && wstr.back() == L'\0') wstr.pop_back(); + return wstr; +} +int main(int argc, char* argv[]) { + // 检查命令行参数是否提供了目录路径 + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + set_console_utf8(); + Base_Logger_Manager::instance().init(); + auto path = Psc::get_exe_dir() + "/tarck_logs/mode_s.log"; + mode_s_logger = new BaseLogger(path, 100 * 100 * 10000, 1); + std::string aaa = argv[1]; + std::u8string dp((char8_t*)platform_2_utf8(aaa).data()); + auto dir_path = std::filesystem::path(dp); + if (!fs::exists(dir_path)) { + std::cerr << "!fs::exists(pa)." << std::endl; + return 1; + } + if (!fs::is_directory(dir_path)) { + std::cerr << "!fs::is_directory(pa)." << std::endl; + return 1; + } + // 遍历目录下的所有文件 + // for (const auto& entry : fs::directory_iterator(dir_path)) { + // std::string file_path = entry.path().string(); + // std::string filename = entry.path().filename().string(); + // std::cout << "Full path: " << file_path << std::endl; + // std::cout << "File name: " << filename << std::endl; + // handle(file_path, filename); + // } + list_directory(dir_path, dir_path); + return 0; +} +// std::vector maybe_dir_path_list { +// dir_path, +// gbk_to_utf8(dir_path) +// }; +// bool find = false; +// for (auto& maybe_dir_path : maybe_dir_path_list) { +// if (fs::exists(maybe_dir_path) && fs::is_directory(maybe_dir_path)) { +// dir_path = maybe_dir_path; +// find = true; +// break; +// } +// } +// if (!find) { +// std::cerr << "The path does not exist or is not a directory." << std::endl; +// return 1; +// } diff --git a/module/mlat_source/main.h b/module/mlat_source/main.h new file mode 100644 index 0000000..3d00367 --- /dev/null +++ b/module/mlat_source/main.h @@ -0,0 +1,27 @@ + +#define MY_LIBRARY_EXPORTS +#ifdef _WIN32 // Windows 平台 + #ifdef MY_LIBRARY_EXPORTS // 如果正在构建库 + #define MY_LIBRARY_API __declspec(dllexport) // 导出符号 + #else + #define MY_LIBRARY_API __declspec(dllimport) // 导入符号 + #endif +#else // 对于 Linux 和其他 Unix-like 系统 + #define MY_LIBRARY_API __attribute__((visibility("default"))) // 导出符号 +#endif +#include + + +#include +extern "C" { +#pragma pack(push, 8) + struct MLAT_Data { + char icao[6]; + double lon; + double lat; + }; +#pragma pack(pop) + MY_LIBRARY_API void read(MLAT_Data* buf, std::uint16_t* len); +} + + diff --git a/module/mlat_source/mlat_dll.cpp b/module/mlat_source/mlat_dll.cpp new file mode 100644 index 0000000..4c54ca1 --- /dev/null +++ b/module/mlat_source/mlat_dll.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +#include + +#include "main.h" +#include +#include // 蹇呴』鍖呭惈杩欎釜澶存枃浠朵互浣跨敤 std::memcpy +#include +#include +#include +#include +#include + +using Func_Type = void(*)(MLAT_Data* buf, std::uint16_t* len); +Func_Type read_func_ptr{}; + + +double lon = 121.473701; +double lat = 121.473701; + +// void read(MLAT_Data* buf, std::uint16_t* len) { +// // 第二条 +// strncpy(buf[0].icao, "XYZ789", sizeof(buf[1].icao)); +// buf[0].lon = lon; +// buf[0].lat = lat; +// lon += 0.001; +// lat += 0.001; +// +// *len = 1; +// } +time_t datetime_to_timestamp(const std::string& datetime) { + std::tm tm = {}; + std::istringstream ss(datetime); + ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); + if (ss.fail()) { + throw std::runtime_error("时间格式错误"); + } + // std::mktime按本地时区转换 + return std::mktime(&tm); +} + +struct Base_MLAT_Data { + MLAT_Data mlat_data; + std::string msg; +}; + +std::vector read_lines() { + Base_Logger_Manager::instance()->init(); + std::multimap ordered_multimap; + std::ifstream fin("C:/wyc/CLionProjects/plantSunCat/mainProjects/Radarcape/data/0701/info.txt"); + if (!fin) { + std::cerr << "无法打开文件!" << std::endl; + return {}; + } + std::string line; + while (std::getline(fin, line)) { + if (line.empty()) continue; + + std::vector fields; + std::stringstream ss(line); + std::string item; + while (std::getline(ss, item, ';')) { + fields.push_back(item); + } + // 检查字段数 + if (fields.size() < 9) continue; + // 2025-07-03 10:16:21;VG_MSDW;28069B;2025-06-27 08:00:14;A800103FFF9CCB27FFFCA89DE0D3;121.691398;37.344925;8000;;0000;2_1_3 + // 按 1-base 提取 + std::string icao = fields[2]; + icao.resize(6, '_'); + std::string msg = fields[4]; + std::string lon = fields[5]; + std::string lat = fields[6]; + std::string alt = fields[7]; + + + std::string time = fields[3]; + // 输出(或者你可以存到 vector 里) + // std::cout << icao << " " << lat << " " << lon << " " << alt << std::endl; + + Base_MLAT_Data b; + //cur.icao = icao; + b.mlat_data.lon = stod(lon); + b.mlat_data.lat = stod(lat); + b.msg = line + "\n"; + + + //std::cout << cur.lat << " " << cur.lon << std::endl; + // if (35 < cur.lat && cur.lat < 37 + // && + // 120 < cur.lon && cur.lon < 122 + // ) { + // + // pure_log("@/logs/mlat_.log", line + "\n"); + // } + + + size_t icao_size = icao.size(); + + + strncpy(b.mlat_data.icao, icao.c_str(), 6); + ordered_multimap.insert({datetime_to_timestamp(time), b}); + } + std::vector list; + for (auto it : ordered_multimap) { + Base_MLAT_Data& b = it.second; + list.push_back(b.mlat_data); + auto icao = std::string(b.mlat_data.icao, 6); + //if (icao == "78139F") + + // 58161E_mlat_result.log + + pure_log("@/logs/icao/" + icao + "_mlat_result.log", b.msg); + + } + return list; +} +std::vector all; + +void read(MLAT_Data* buf, std::uint16_t* len) { + if (all.empty()) { + all = read_lines(); + } + static size_t pos = 0; // 上一次拷贝结束后的位置 + const size_t BATCH = 100; + size_t total = all.size(); + size_t n = std::min(BATCH, total - pos); // 本次实际要拷贝多少 + + for (size_t i = 0; i < n; ++i) { + buf[i] = all[pos + i]; + } + *len = static_cast(n); + + pos += n; + if (pos >= total) { + pos = 0; // 如果需要循环一轮后归零,否则你可以不归零 + } +} + + + + + +int main() { + // std::uint16_t size; + // MLAT_Data arr[1000]; + // read(arr, &size); + + printf("默认最大文件数: %d\n", _getmaxstdio()); + _setmaxstdio(2048); // 可以设置更高(最高8192,部分环境2048) + printf("修改后最大文件数: %d\n", _getmaxstdio()); + + + for (int i = 0; i < 1000; ++i) { + pure_log("@/logs/num/" + std::to_string(i) + ".log", "\n"); + } +} diff --git a/module/test_drogon/main.cpp b/module/test_drogon/main.cpp new file mode 100644 index 0000000..ff2b8fc --- /dev/null +++ b/module/test_drogon/main.cpp @@ -0,0 +1,32 @@ +#include + +using namespace drogon; + +class GlobalMid : public HttpMiddleware { +public: + void invoke(const HttpRequestPtr &req, + MiddlewareNextCallback &&next, + MiddlewareCallback &&done) override { + + LOG_DEBUG << "进入全局中间件:" << req->path(); + next([done](auto resp) { + resp->addHeader("X-Global", "Yes"); + LOG_DEBUG << "离开全局中间件"; + done(resp); + }); + } +}; + +int main() { + app().registerMiddleware(std::make_shared()); + + app().registerHandler("/hello", [](const HttpRequestPtr &request, + std::function &&cb){ + auto resp = HttpResponse::newHttpResponse(); + resp->setBody("OK"); + cb(resp); + }, {Get}); + + app().addListener("0.0.0.0", 80); + app().run(); +} \ No newline at end of file diff --git a/module/test_memory_lose.cpp b/module/test_memory_lose.cpp new file mode 100644 index 0000000..df90a0a --- /dev/null +++ b/module/test_memory_lose.cpp @@ -0,0 +1,194 @@ +#include +#include +#include "Base/export.h" +#include "Bit.h" +#include "Socket/Socket.h" +using Func_Type = void(*)(char* buf, std::uint16_t* len); + + +int main(int argc, char* argv[]) { + bool use_dll = false; + std::string ip = "127.0.0.1"; + uint16_t port = 12345; + // 检查命令行参数数量 + if (argc >= 2) { + // 解析 use_dll 参数 + if (std::string(argv[1]) == "true") { + use_dll = true; + } else if (std::string(argv[1]) == "false") { + use_dll = false; + } + + // 解析 ip 参数(如果有) + if (argc >= 3) { + ip = argv[2]; + } + + // 解析 port 参数(如果有) + if (argc >= 4) { + try { + port = std::stoi(argv[3]); + } catch (const std::invalid_argument& e) { + std::cerr << "Invalid port number." << std::endl; + return 1; + } catch (const std::out_of_range& e) { + std::cerr << "Port number out of range." << std::endl; + return 1; + } + } + } + + + + // 输出解析后的参数 + std::cout << "use_dll: " << (use_dll ? "true" : "false") << std::endl; + std::cout << "ip: " << ip << std::endl; + std::cout << "port: " << port << std::endl; + + Func_Type read_func_ptr = nullptr; + if (use_dll) { + auto path = Psc::get_abs_path("@/x86_linux_Release_lib9002so/lib9002.so"); + auto lib = Psc::load_library(path); + if (lib == nullptr) std::exit(1); + read_func_ptr = (Func_Type)Psc::load_function(lib, "adsb_read"); + if (read_func_ptr == nullptr) std::exit(2); + } + + Base_Logger_Manager::instance().init(); + + + + Psc::socket::Server_Socket socket; + socket.type = Psc::socket::Socket_Type::TCP; + socket.create(); + + + std::thread read_thread([&socket, ip, port]() { + socket.listen(ip, port); + }); + read_thread.detach(); + Psc::socket::socket_logger = new BaseLogger(Psc::get_exe_dir() + "/test_memory_lose.log", 1024 * 1024 * 100, 1); + //auto play_back_logger = new BaseLogger(Psc::get_exe_dir() + "/play_back.log", 1024 * 1024 * 100, 1); + + std::vector clients; + while (true) { + std::string data; + + if (use_dll) { + char buf[std::numeric_limits::max()]; + std::uint16_t len; + read_func_ptr(buf, &len); + data = std::string(buf, len); + socket.flush_clients(); + socket.write_to_all_clients(data); + //play_back_logger->debug({}, {}, data + " "); + + pure_log(Psc::get_exe_dir() + "/play_back.log", data + " " + "\n"); + } else { + + data = std::string(100, '7'); + socket.flush_clients(); + socket.write_to_all_clients(data); + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +int main2(int argc, char* argv[]) { + bool use_dll = false; + std::string ip = "127.0.0.1"; + uint16_t port = 12345; + // 检查命令行参数数量 + if (argc >= 2) { + // 解析 use_dll 参数 + if (std::string(argv[1]) == "true") { + use_dll = true; + } else if (std::string(argv[1]) == "false") { + use_dll = false; + } + + // 解析 ip 参数(如果有) + if (argc >= 3) { + ip = argv[2]; + } + + // 解析 port 参数(如果有) + if (argc >= 4) { + try { + port = std::stoi(argv[3]); + } catch (const std::invalid_argument& e) { + std::cerr << "Invalid port number." << std::endl; + return 1; + } catch (const std::out_of_range& e) { + std::cerr << "Port number out of range." << std::endl; + return 1; + } + } + } + + + + // 输出解析后的参数 + std::cout << "use_dll: " << (use_dll ? "true" : "false") << std::endl; + std::cout << "ip: " << ip << std::endl; + std::cout << "port: " << port << std::endl; + + Func_Type read_func_ptr = nullptr; + if (use_dll) { + auto path = Psc::get_abs_path("@/x86_linux_Release_lib9002so/lib9002.so"); + auto lib = Psc::load_library(path); + if (lib == nullptr) std::exit(1); + read_func_ptr = (Func_Type)Psc::load_function(lib, "adsb_read"); + if (read_func_ptr == nullptr) std::exit(2); + } + + + + + Psc::socket::Client_Socket socket; + socket.type = Psc::socket::Socket_Type::TCP; + socket.create(); + Psc::socket::Sockaddr_In addr; + if (use_dll) { + addr = Psc::socket::Sockaddr_In{ip, port}; + } else { + addr = Psc::socket::Sockaddr_In{ip, port}; + } + + + std::cout << "connect:" << addr.to_string() << std::endl; + socket.set_dest_address(addr); + bool c = socket.connect(); + while (true) { + + std::string data; + if (use_dll) { + char buf[std::numeric_limits::max()]; + std::uint16_t len; + read_func_ptr(buf, &len); + data = std::string(buf, len); + } else { + data = std::string(100, '7'); + } + + + + + // if (data.size() < 500) { + // std::cout << "read[" << data.size() << "]:" << memory2hex(data) << std::endl; + // } else { + // std::cout << "read[" << data.size() << "]:" << std::endl; + // } + //std::cout << "read[" << data.size() << "]:" << std::endl; + + int send = socket.send(data); + if (send == -1) { + if (Psc::socket::is_needed_reconnect(socket.socket_fd)) { + std::cout << socket.socket_fd << " 重新连接 " << std::endl; + socket.connect(); + } + + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} diff --git a/script/api.http b/script/api.http new file mode 100644 index 0000000..6603ce9 --- /dev/null +++ b/script/api.http @@ -0,0 +1,476 @@ + + +# @ip = 192.168.1.175 +# @port = 9081 +# @prefix = api/ + +@ip = localhost +@port = 80 +@prefix = api/ + +### aircraftlist.json +GET http://{{ip}}:{{port}}/aircraftlist.json + +### aircraftlist.json +GET http://192:168.205.116/aircraftlist.json + + + +### aircraft_list aircraftlist.json 默认版本使用默认数据源 +GET http://192.168.205.116:9081/aircraftlist.json + + +### aircraft_list aircraftlist.json 默认版本使用默认数据源 +GET http://27.216.218.236:9081/aircraftlist.json + +### aircraft_list aircraftlist.json 的带数据源版本 +POST http://{{ip}}:{{port}}/{{prefix}}aircraft_list +Authorization: 123 + +{ + "data_source_key": "binary_data48__2025-01-06.222333" +} + + + + + +### 获取错误日志 +POST http://{{ip}}:{{port}}/{{prefix}}get_debug_log + +{ + "serial_number": 3 +} + + +### 获取错误日志 +POST http://{{ip}}:{{port}}/{{prefix}}get_mode_s_basic_config + +{ +} + + + + + +### 获取飞机基础信息 +POST http://{{ip}}:{{port}}/{{prefix}}get_aircraft_base_info + +{ + "data_source_key": "radcap24", + "icao": "71C510" +} + +### 获取飞机详细信息 +POST http://{{ip}}:{{port}}/{{prefix}}get_general_info + + + +### 获取飞机详细信息 +POST http://{{ip}}:{{port}}/{{prefix}}get_aircraft_detail_info + +{ + "data_source_key": "radcap24", + "icao": "71C510" +} + +### 获取飞机轨迹 最后的几个 +POST http://{{ip}}:{{port}}/{{prefix}}get_aircraft_track_list + +{ + "data_source_key": "binary_data48__2025-01-06.2", + "icao": "780C6D", + "last_size":106 +} + + + +### 获取飞机在某个时间点之后 +POST http://{{ip}}:{{port}}/{{prefix}}get_aircraft_list_after +Authorization: 123 + +{ + "timestamp": 1743407092 +} + + +### 请求飞机变更列表 +POST http://{{ip}}:{{port}}/{{prefix}}aircraft_change_list +Authorization: 123 + +{ + "data_source_key": "binary_data48__2025-01-06.222333" +} + + + + +#### 版本 +POST http://{{ip}}:{{port}}/{{prefix}}version +Authorization: 123 + +### 登录 +#{ +# "status": 200, +# "data": { +# "status": true +# } +#} +POST http://{{ip}}:{{port}}/{{prefix}}login +Content-Type: application/json +Authorization: 123 + +{ + "account": "admin", + "password": "123" +} + +### 获取多点服务器配置 +POST http://{{ip}}:{{port}}/{{prefix}}get_mlat_server_config + + +### 设置多点服务器配置 +POST http://{{ip}}:{{port}}/{{prefix}}set_mlat_server_config + +{ + "ip": "192.168.1.24", + "port": 1111, + "id": 1 +} + + + +### 基站位置 +POST http://{{ip}}:{{port}}/{{prefix}}get_base_station_location +Authorization: 123 + +{ + "data_source_key": "radcap254" +} + + +### 获取 设备配置 +POST http://{{ip}}:{{port}}/{{prefix}}get_device_config +Content-Type: application/json +Authorization: 123 + +### +GET http://192.168.208.191/ui/map +### +GET http://192.168.208.191/ + + + + +### 设置 设备配置 +POST http://{{ip}}:{{port}}/{{prefix}}set_device_config +Content-Type: application/json +Authorization: 123 + +{ + "net": [ + { + "key": "device", + "ip": "192.168.1.193", + "netmask": "255.255.255.0", + "gateway": "192.168.1.1", + "port": 80 + }, + { + "key": "wifi", + "ip": "192.168.10.193", + "netmask": "255.255.255.0", + "gateway": "192.168.10.1", + "port": 80 + } + ] +} + +### 获取 数据源 整体 +POST http://{{ip}}:{{port}}/{{prefix}}get_mode_s_data_source_config + +### 获取基站 +POST http://{{ip}}:{{port}}/{{prefix}}get_base_station_location + +{ + "data_source_key": "radcap254" +} + + + + +### 获取 全部 数据馈送 端口配置 +@data_feed_name = data_feed + +POST http://{{ip}}:{{port}}/{{prefix}}get_data_feed_config +Content-Type: application/json +Authorization: 123 + + + + +### 获取 全部 数据馈送 端口配置 +POST http://{{ip}}:{{port}}/{{prefix}}aircraft_change_list +Content-Type: application/json + + +### 设置数据馈送端口配置 +POST http://{{ip}}:{{port}}/{{prefix}}update_{{data_feed_name}} +Content-Type: application/json +Authorization: 123 + +{ + "key": "Port_10005", + "enable": false, + "net_send_type": "TCP_Client", + "url": "0.0.0.0", + "port": 10005, + "output_format": "BIN", + "use_status": true, + "mode_s_output_type": "NO_POS_Mode_S", + "use_mode_ac": false +} + +### 查 +POST http://{{ip}}:{{port}}/{{prefix}}search_{{data_feed_name}} +Content-Type: application/json +Authorization: 123 + +{ + "key": "Port_10003" +} + + + + +### 数据源-数据馈送-关系配置相关项目 +@relation_name = source_feed_relation +### 查全体 +POST http://{{ip}}:{{port}}/{{prefix}}get_{{relation_name}}_config + + +### 查 +POST http://{{ip}}:{{port}}/{{prefix}}search_{{relation_name}} + +{ + "key":"1" +} + +### 增 +POST http://{{ip}}:{{port}}/{{prefix}}insert_{{relation_name}} + +### 删 +POST http://{{ip}}:{{port}}/{{prefix}}remove_{{relation_name}} + +### 改 +POST http://{{ip}}:{{port}}/{{prefix}}update_{{relation_name}} + +### 上移 +POST http://{{ip}}:{{port}}/{{prefix}}rise_{{relation_name}} + +### 下移 +POST http://{{ip}}:{{port}}/{{prefix}}fall_{{relation_name}} + + +### 多点定位相关接口 +POST http://{{ip}}:{{port}}/{{prefix}}get_mlat_list + + + +### 多点定位相关接口 +GET http://192.168.1.75/tiles/0.png +### +GET http://192.168.1.75/tiles/3/-3/1.png + + +### 多点定位相关接口 +GET http://127.0.0.1/tiles/0.png + + + + +// DSP 相关接口 + +### DSP 获取数据 +POST http://{{ip}}:{{port}}/{{prefix}}get_dsp_data +Authorization: 123 + +### 更新 +POST http://{{ip}}:{{port}}/{{prefix}}update_dsp +Authorization: 123 + +{ + "port": 80 + "center_freq": 80 + "band_width": 80 + "sample_rate": 80 +} + +### 获取 +POST http://{{ip}}:{{port}}/{{prefix}}get_dsp_config +Authorization: 123 + +### 获取 +POST http://{{ip}}:{{port}}/{{prefix}}get_dsp_data + +### 获取 +GET http://127.0.0.1:80/hello + +### 获取 +GET http://{{ip}}:{{port}} +### 获取 +GET http://{{ip}}:{{port}}/tiles/3/-3/1.png + +### 获取 +GET http://{{ip}}:{{port}}/assets/index-emIKMKOm.js + +### +GET http://{{ip}}:{{port}}/lightningchart-js/lcjs.theme.js +### +http://localhost:8848/api/method/test + +### 获取 +POST http://192.168.1.110:9081/api/get_dsp_data + + +### 开始录制 +POST http://{{ip}}:{{port}}/{{prefix}}start_record_iq_stream + +{ + dir: "C:\wyc\plantSunCat\build" + file_name: "test.txt" +} + + +### 结束录制 +POST http://{{ip}}:{{port}}/{{prefix}}end_record_iq_stream + +### 结束录制 +GET http://{{ip}}:{{port}}/aircraftlist.json + +### 结束录制 +POST http://{{ip}}:{{port}}/download_iq + +### + +POST http://{{ip}}:{{port}}/{{prefix}}get_dsp_config + + + +### + +GET http://{{ip}}:{{port}}/get_debug_info + +### + +POST http://{{ip}}:{{port}}/{{prefix}}version + +### +GET http://192.168.1.112:9081/get_debug_info + +### +GET http://{{ip}}:{{port}}/tiles/3/-2/-1.png + + +### + +POST http://{{ip}}:{{port}}/let_oom + +{ +} + +### + +POST http://{{ip}}:{{port}}/let_dump + +{ +} + +### +POST http://192.168.1.114:9081/let_dump + +{ +} + +### + +POST http://192.168.1.110:9081/api/version + +### +POST http://192.168.1.114:9081/let_oom + +{ +} + + +### +POST http://192.168.204.116:9081/api/get_data_feed_server_connect + +{ + "key": "mode_acs_shared_memory" +} + + +### +POST http://{{ip}}:{{port}}/{{prefix}}get_data_source_connect + +{ + "key": "mode_acs_shared_memory" +} + + + +#### 在线升级 +#POST http://{{ip}}:{{port}}/{{prefix}}import_version +#Content-Type: multipart/form-data; boundary=----CLION_BOUNDARY +# +#------CLION_BOUNDARY +#Content-Disposition: form-data; name="file"; filename="v1.0.2.zip" +#Content-Type: application/octet-stream +# +#< C:\Users\wyc\Desktop\version_control\v1.0.2.zip +#------CLION_BOUNDARY-- + + +### 在线升级(纯内存解压) +POST http://192.168.1.102:9081/{{prefix}}import_version +Content-Type: application/zip +dir: v1.0.2 + +< C:/Users/wyc/Desktop/version_control/v1.0.2.zip + +### 在线升级(纯内存解压) +POST http://{{ip}}:{{port}}/{{prefix}}import_version +Content-Type: application/zip +dir: v1.0.2 + +< C:/Users/wyc/Desktop/version_control/v1.0.2.zip + +### 导出日志(下载 logs) +POST http://{{ip}}:{{port}}/{{prefix}}export_logs + +### 导出版本(下载 zip) +POST http://{{ip}}:{{port}}/{{prefix}}export_version + + + + +### 重启设备 +POST http://192.168.1.102:9081/{{prefix}}restart_device + + +### +GET https://planespotters.live/api/aircraft/hex/780F0E?icaoType=B738 + + +### +GET https://planespotters.live/api/aircraft/hex/780F0E + +### +GET https://planespotters.live/api/radar/trace/8963e5 + +### +GET https://planespotters.live/api/radar/trace/780F0E + +### +GET https://t.plnspttrs.net/09923/1733381_73bcce8cad_280.jpg \ No newline at end of file diff --git a/script/old/ecap.service b/script/old/ecap.service new file mode 100644 index 0000000..a44f160 --- /dev/null +++ b/script/old/ecap.service @@ -0,0 +1,48 @@ +[Unit] +Description=ecap_server Daemon +After=network.target +Requires=heco22.service +After=heco22.service +BindsTo=heco22.service + + +# 防止启动失败 → 无限重启风暴 频率限制在 5 次 / 60 秒 +StartLimitIntervalSec=60 +StartLimitBurst=5 + +[Service] +Type=simple +ExecStartPre=/bin/bash -c 'until ping -c 1 172.22.0.2 > /dev/null 2>&1; do echo "Waiting for network..."; sleep 3; done' +ExecStart=/home/heco/ys_ecap_server/ecap_server +WorkingDirectory=/home/heco/ys_ecap_server + +# 只有失败时才重启 +Restart=on-failure +RestartSec=5 + +# 明确指定正常退出(0)时,不会重启 +SuccessExitStatus=0 75 +# 进程异常退出时重启 如果进程返回RestartPreventExitStatus时,不会重启 +RestartPreventExitStatus=2 3 + + + +# 或系统关机 / 重启时,systemd 首先发送给该服务主进程的信号。 +KillSignal=SIGTERM +TimeoutStopSec=30 +# 进程文件描述符数量 +LimitNOFILE=65535 +# cat /proc/sys/kernel/core_pattern 针对单个进程 生成 core dump 等价于 ulimit -c unlimited +LimitCORE=infinity + +# 该服务进程 以及它 fork 的所有子进程,在运行期间 不能获得任何“新的权限”。 +NoNewPrivileges=yes +# # 隔离临时目录 +PrivateTmp=yes + +# 把服务进程的 标准输出(stdout), 标准错误(stderr) 重定向到 systemd journal。 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/script/old/heco22.service b/script/old/heco22.service new file mode 100644 index 0000000..593277f --- /dev/null +++ b/script/old/heco22.service @@ -0,0 +1,47 @@ +[Unit] +Description=heco22 Daemon +After=network.target +PartOf=ecap.service + + + + +# 防止启动失败 → 无限重启风暴 频率限制在 5 次 / 60 秒 +StartLimitIntervalSec=60 +StartLimitBurst=5 + +[Service] +Type=simple +ExecStart=/home/heco/ys_ecap_server/start_22.sh + + +# 只有失败时才重启 +Restart=on-failure +RestartSec=5 + +# 明确指定正常退出(0)时,不会重启 +SuccessExitStatus=0 75 +# 进程异常退出时重启 如果进程返回RestartPreventExitStatus时,不会重启 +RestartPreventExitStatus=2 3 + + + +# 或系统关机 / 重启时,systemd 首先发送给该服务主进程的信号。 +KillSignal=SIGTERM +TimeoutStopSec=30 +# 进程文件描述符数量 +LimitNOFILE=65535 +# cat /proc/sys/kernel/core_pattern 针对单个进程 生成 core dump 等价于 ulimit -c unlimited +LimitCORE=infinity + +# 该服务进程 以及它 fork 的所有子进程,在运行期间 不能获得任何“新的权限”。 +NoNewPrivileges=yes +# # 隔离临时目录 +PrivateTmp=yes + +# 把服务进程的 标准输出(stdout), 标准错误(stderr) 重定向到 systemd journal。 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/script/rc.local b/script/rc.local new file mode 100644 index 0000000..a1858e0 --- /dev/null +++ b/script/rc.local @@ -0,0 +1,5 @@ +#!/bin/bash + +/home/heco/ys_ecap_server/protect.bash & + +exit 0 diff --git a/script/scp.bash b/script/scp.bash new file mode 100644 index 0000000..f1264c4 --- /dev/null +++ b/script/scp.bash @@ -0,0 +1,53 @@ +#!/bin/bash + +# 第一次要手动连入 通过下面的测试 +# ** WARNING: connection is not using a post-quantum key exchange algorithm. +# ** This session may be vulnerable to "store now, decrypt later" attacks. +# ** The server may need to be upgraded. See https://openssh.com/pq.html + + + +# C:\msys64\msys2_shell.cmd +# -mingw64 -defterm -no-start -here + + +ver_dir_name="v1.0.3" +dstHost="192.168.1.101" + + +tmp_dir="/c/Users/wyc/Desktop/version_control/tmp" +file_name="ecap_server" + +srcUser="wyc" +srcHost="192.168.1.8" +src_dir="/aenv/proj/plant_sun_cat/cmake-build-ubuntu_20_04-release2" +SRC_PASS="123" + +dstUser="heco" + +dst_dir="/home/heco/ys_ecap_server/${ver_dir_name}" +DST_PASS="123456" + + +sshpass -p "123" scp -o StrictHostKeyChecking=no "${srcUser}@${srcHost}:${src_dir}/${file_name}" "${tmp_dir}" +sshpass -p "123456" scp -o StrictHostKeyChecking=no "${tmp_dir}/${file_name}" "${dstUser}@${dstHost}:${dst_dir}" + + +#expect + +# 合并执行 +#export SSHPASS=$SRC_PASS +#sshpass -e ssh "${srcUser}@${srcHost}" "cat ${src_dir}/${file_name}" | \ +# sshpass -p "$DST_PASS" ssh "${dstUser}@${dstHost}" "cat > ${dst_dir}/${file_name}" +# + + +# 检查 SCP 命令的返回值 +if [ $? -eq 0 ]; then + echo "传输成功!" +else + echo "传输失败:$?" +fi + +#echo "按任意键继续..." +#read -n 1 -s \ No newline at end of file diff --git a/script/scp.ps1 b/script/scp.ps1 new file mode 100644 index 0000000..58622d0 --- /dev/null +++ b/script/scp.ps1 @@ -0,0 +1,31 @@ +chcp 65001 + + + +$srcUser = "wyc" +$srcHost = "192.168.1.8" +$srcPath = "/aenv/proj/plant_sun_cat/cmake-build-ubuntu_20_04-release2/ecap_server" + +$dstUser = "heco" +$dstHost = "192.168.1.102" +$dstPath = "/home/heco/ys_ecap_server/v1.0.2/" +#$dstPath = "/home/heco/ys_ecap_server/test/" + + + + +#scp "${srcUser}@${srcHost}:${srcPath}" "${dstUser}@${dstHost}:${dstPath}" + + +#$dstPath = "C:\Users\wyc\Desktop\version_control" + +scp "vm_ubuntu_2004:${srcPath}" "${dstUser}@${dstHost}:${dstPath}" +#scp "vm_ubuntu_2004:${srcPath}" "$dstPath" + + + +if ($LASTEXITCODE -eq 0) { + Write-Host "SCP_DONE OK!" +} else { + Write-Host "SCP_FAILED:$LASTEXITCODE" +} \ No newline at end of file diff --git a/script/scp_wwwroot.bash b/script/scp_wwwroot.bash new file mode 100644 index 0000000..82cdb0c --- /dev/null +++ b/script/scp_wwwroot.bash @@ -0,0 +1,33 @@ +#!/bin/bash + +# 第一次要手动连入 通过下面的测试 +# ** WARNING: connection is not using a post-quantum key exchange algorithm. +# ** This session may be vulnerable to "store now, decrypt later" attacks. +# ** The server may need to be upgraded. See https://openssh.com/pq.html + + + +file_name="wwwroot" + + +ver_dir_name="v1.0.3" +dstHost="192.168.1.102" + +src_dir="C:\Users\wyc\Desktop\version_control\${ver_dir_name}" +dstUser="heco" +dstHost="192.168.1.102" +dst_dir="/home/heco/ys_ecap_server/${ver_dir_name}" +DST_PASS="123456" + + +sshpass -p "$DST_PASS" ssh "${dstUser}@${dstHost}" "rm -rf '${dst_dir}/${file_name}'" +sshpass -p "$DST_PASS" scp -o StrictHostKeyChecking=no -r "${src_dir}/${file_name}" "${dstUser}@${dstHost}:${dst_dir}/${file_name}" + + + +# 检查 SCP 命令的返回值 +if [ $? -eq 0 ]; then + echo "传输成功!" +else + echo "传输失败:$?" +fi diff --git a/script/start_22.sh b/script/start_22.sh new file mode 100644 index 0000000..721d8f6 --- /dev/null +++ b/script/start_22.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +sudo supervisorctl stop hs +sudo rm /dev/shm/* +cd /home/heco/HECO/HECOService +export LD_LIBRARY_PATH=/home/heco/HECO/HECOService:$LD_LIBRARY_PATH +./HECOService + + + + + +# /usr/bin/supervisorctl stop hs +# /usr/bin/supervisorctl stop hs + +#sudo supervisorctl reread +#sudo supervisorctl update +# sudo systemctl daemon-reload + +# 版本切换设置 +# /etc/systemd/system/ecap.service +# sudo systemctl daemon-reload +# sudo systemctl start ecap +# systemctl status ecap +# sudo systemctl enable ecap +# systemctl is-enabled ecap +# 停止 +#sudo systemctl stop ecap +# 重启 +#sudo systemctl restart ecap +# 重新加载(程序不重启) +#sudo systemctl reload ecap # 前提是程序支持 SIGHUP +# 查看日志 +#journalctl -u ecap +#journalctl -u ecap -f +# echo 'ECAP_DIR=/home/heco/ys_ecap_server/v1.0.2' | sudo tee -a /etc/ecap/env + + +# ln -sf /home/heco/ys_ecap_server/v1.0.2/ecap_server /home/heco/ys_ecap_server + + + +# ps -ef | grep start_22.sh + + + +# pstree -sp $(pgrep -o HECOService) + +# # pstree -sp 712 + + +# systemd(1)───sshd(655)───sshd(1788)───sshd(1876)───bash(1898)───bash(18630)───HECOService(18641)─┬─{HECOService}(18642) +# ├─{HECOService}(18660) +# ├─{HECOService}(18661) +# └─{HECOService}(18662) + + + +# systemd(1)───sshd(655)───sshd(1788)───sshd(1876)───bash(1898)───HECOService(20409)─┬─curl(20451) +# ├─{HECOService}(20425) +# ├─{HECOService}(20448) +# ├─{HECOService}(20449) +# └─{HECOService}(20450) + + + +# systemd(1)───HECOService(41201)─┬─{HECOService}(41218) +# ├─{HECOService}(41242) +# ├─{HECOService}(41243) +# └─{HECOService}(41244) diff --git a/script/upgrade.ps1 b/script/upgrade.ps1 new file mode 100644 index 0000000..f8ba4da --- /dev/null +++ b/script/upgrade.ps1 @@ -0,0 +1,15 @@ +# upgrade.legacy +# 打印传入的所有参数 + +Write-Host "upgrade.legacy called" +Write-Host "Argument count: $($args.Count)" + +for ($i = 0; $i -lt $args.Count; $i++) { + Write-Host "arg[$i] = $($args[$i])" +} + +# 也可以单独按语义取(如果你约定了顺序) +# $version = $args[0] +# $dir = $args[1] +# Write-Host "version = $version" +# Write-Host "dir = $dir" diff --git a/script/重启fpga b/script/重启fpga new file mode 100644 index 0000000..6e7ae9b --- /dev/null +++ b/script/重启fpga @@ -0,0 +1,14 @@ +ssh root@172.22.0.2 /sbin/reboot + + +analog + + +ping 172.22.0.2 + + +// 看服务重启次数 +systemctl show ecap -p NRestarts + +// 看失败历史 +systemctl show ecap -p ExecMainStatus -p ExecMainStartTimestamp diff --git a/ssh b/ssh new file mode 100644 index 0000000..ea2ee90 --- /dev/null +++ b/ssh @@ -0,0 +1,6 @@ +ssh -p 35024 -N -L 1234:127.0.0.1:1234 heco@192.168.101.7 + + + + +sudo -E gdb --args ./v1.1.2/ecap_server main @/config_mlat.json \ No newline at end of file diff --git a/todolist.txt b/todolist.txt new file mode 100644 index 0000000..2eefc50 --- /dev/null +++ b/todolist.txt @@ -0,0 +1,10 @@ +@D:/ae/proj/projects/ECAP_Server/module/Local_Server/server/libarchive.h#L31-35 这个文件 不合规则的检查 不和规则应该是意料之中的 所以 不符合规则用 expectd@D:/ae/proj/projects/ECAP_Server/module/Local_Server/server/libarchive.h#L9 返回结果 + +同时 + +@D:/ae/proj/projects/ECAP_Server/module/Local_Server/server/server.cpp#L313-317 这个位置检查结果 不和规则 后端给出明确的不和规原因 + + +#include "Core/Base/expected.h" using unexpected = tl::unexpected; + +使用的是这里的包装expected \ No newline at end of file diff --git a/初版升级步骤.txt b/初版升级步骤.txt new file mode 100644 index 0000000..cdcd6fc --- /dev/null +++ b/初版升级步骤.txt @@ -0,0 +1,21 @@ +# 关闭正在运行的守护进程 +sudo pkill -9 pt +sudo pkill -9 protect.bash +sudo pkill -9 ecap_server + +# 删除旧的守护进程配置 +sudo systemctl disable rc-local.service +sudo systemctl stop rc-local.service +删除 /home/heco/ys_ecap_server 下 protect.bash + + +# 添加新的守护进程配置 +ecap.service 复制到 /etc/systemd/system 更改权限777 +sudo systemctl daemon-reload +sudo systemctl enable ecap +复制文件夹 lib v1.0.2,复制文件 restart_device.bash upgrade.bash 到 /home/heco/ys_ecap_server 全部更改权限777 +ln -sf /home/heco/ys_ecap_server/v1.0.2/ecap_server /home/heco/ys_ecap_server/ecap_server 全部更改权限777 + + +# 启动新的守护进程 +sudo systemctl start ecap \ No newline at end of file diff --git a/多点定位数据模拟以及算法规范.txt b/多点定位数据模拟以及算法规范.txt new file mode 100644 index 0000000..bd8977f --- /dev/null +++ b/多点定位数据模拟以及算法规范.txt @@ -0,0 +1,28 @@ + + + + +模拟出三个基站 + + +模拟一条飞机的航线 一个列表 一堆轨迹点 算没到达一个位置与各个时间戳值 +发送 做定位 + + + +# 使用 objcopy 命令将字符串嵌入到 ELF 文件的 .note 部分 +echo -n "Author: John Doe\nVendor: MyCompany\nVersion: 1.0.0" > metadata.txt + +# 将字符串内容直接嵌入 ELF 文件 +objcopy --add-section .note.metadata=metadata.txt ./rls_cxx_Linux_Debug + + +readelf -d + + +readelf -p .info./rls_cxx_Linux_Debug +readelf -S ./rls_cxx_Linux_Debug + + +objdump -s --section=.note.metadata ./rls_cxx_Linux_Debug +objdump -s ./rls_cxx_Linux_Debug diff --git a/多进程隔离.txt b/多进程隔离.txt new file mode 100644 index 0000000..8f45c9b --- /dev/null +++ b/多进程隔离.txt @@ -0,0 +1,15 @@ + +代码用宏来隔离 +1. dsp 共享内存数据隔离实现完成 单侧dsp adsb先放一下,开始测试 + +2. 拓展数据源 一个共享内存数据源,接收adsb 测试数据 + + + +sudo nohup valgrind --leak-check=full --track-origins=yes ./ecap_server > valgrind_output.log 2>&1 & + + + +heco@heco:~/ys_ecap_server/ecap_1201$ sudo nohup valgrind --leak-check=full --track-origins=yes ./ecap_server > ./valgrind_output222.log 2>&1 + +