summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlijia <[email protected]>2024-04-08 09:48:13 +0800
committerlijia <[email protected]>2024-04-08 09:48:13 +0800
commit215e383be1f47cd18c235855d0cee0485f6cb423 (patch)
tree34668fd59622c37826c3a786ba0e196a7d65147b
parentea795e9c6940281bf8557bfd79f13f319f947c58 (diff)
Separate from stellar-on-sapp project.
-rw-r--r--.gitignore8
-rw-r--r--.gitlab-ci.yml241
-rw-r--r--CMakeLists.txt92
-rw-r--r--autorevision.sh1260
-rw-r--r--ci/.gitkeep0
-rw-r--r--ci/get-nprocessors.sh48
-rw-r--r--ci/travis.sh49
-rw-r--r--cmake/.gitkeep0
-rw-r--r--cmake/Package.cmake43
-rw-r--r--cmake/PostInstall.in1
-rw-r--r--cmake/PostUninstall.in1
-rw-r--r--cmake/PreInstall.in22
-rw-r--r--cmake/PreUninstall.in6
-rw-r--r--cmake/Version.cmake47
-rw-r--r--conf/.gitkeep0
-rw-r--r--conf/http_decoder.toml15
-rw-r--r--deps/.gitkeep0
-rw-r--r--deps/mempool/nmx_alloc.c44
-rw-r--r--deps/mempool/nmx_alloc.h16
-rw-r--r--deps/mempool/nmx_palloc.c262
-rw-r--r--deps/mempool/nmx_palloc.h58
-rw-r--r--deps/toml/CMakeLists.txt3
-rw-r--r--deps/toml/toml.c2379
-rw-r--r--deps/toml/toml.h175
-rw-r--r--deps/uthash/utarray.h248
-rw-r--r--deps/uthash/uthash.h1138
-rw-r--r--deps/uthash/utlist.h1073
-rw-r--r--deps/uthash/utringbuffer.h108
-rw-r--r--deps/uthash/utstack.h88
-rw-r--r--deps/uthash/utstring.h407
-rw-r--r--include/http_decoder.h118
-rw-r--r--readme.md3
-rw-r--r--src/.gitkeep0
-rw-r--r--src/CMakeLists.txt18
-rw-r--r--src/http_content_decompress.c268
-rw-r--r--src/http_content_decompress.h52
-rw-r--r--src/http_decoder.c858
-rw-r--r--src/http_decoder_half.c1034
-rw-r--r--src/http_decoder_half.h119
-rw-r--r--src/http_decoder_inc.h53
-rw-r--r--src/http_decoder_result_queue.c172
-rw-r--r--src/http_decoder_result_queue.h73
-rw-r--r--src/http_decoder_string.c277
-rw-r--r--src/http_decoder_string.h95
-rw-r--r--src/http_decoder_table.c556
-rw-r--r--src/http_decoder_table.h99
-rw-r--r--src/http_decoder_utils.c25
-rw-r--r--src/http_decoder_utils.h66
-rw-r--r--src/version.map9
-rw-r--r--test/.gitkeep0
-rw-r--r--test/CMakeLists.txt149
-rw-r--r--test/http_decoder_driver.cpp923
-rw-r--r--test/http_decoder_gtest.cpp370
-rw-r--r--test/http_decoder_gtest.h114
-rw-r--r--test/http_decoder_stub.cpp250
-rw-r--r--test/http_pcap/http_6over4_single_trans.pcapbin0 -> 1424 bytes
-rw-r--r--test/http_pcap/http_chunked_res_gzip.pcapbin0 -> 29517 bytes
-rw-r--r--test/http_pcap/http_connect_flood.pcapbin0 -> 31672 bytes
-rw-r--r--test/http_pcap/http_get_encoded_uri.pcapbin0 -> 35174 bytes
-rw-r--r--test/http_pcap/http_get_long_cookie.pcapbin0 -> 4489 bytes
-rw-r--r--test/http_pcap/http_get_malformed.pcapbin0 -> 2737 bytes
-rw-r--r--test/http_pcap/http_get_multi_trans.pcapbin0 -> 35841 bytes
-rw-r--r--test/http_pcap/http_get_req_pipeline.pcapbin0 -> 2251 bytes
-rw-r--r--test/http_pcap/http_get_single_trans.pcapbin0 -> 1359 bytes
-rw-r--r--test/http_pcap/http_hdr_truncated_after_kv.pcapbin0 -> 155839 bytes
-rw-r--r--test/http_pcap/http_hdr_truncated_in_kv.pcapbin0 -> 5493 bytes
-rw-r--r--test/http_pcap/http_hdr_value_empty.pcapbin0 -> 1718 bytes
-rw-r--r--test/http_pcap/http_hdrs_exceed_maximum.pcapbin0 -> 5029 bytes
-rw-r--r--test/http_pcap/http_multi_parse_error.pcapbin0 -> 1980 bytes
-rw-r--r--test/http_pcap/http_no_content_length.pcapbin0 -> 1944 bytes
-rw-r--r--test/http_pcap/http_over_pppoe.pcapbin0 -> 1991 bytes
-rw-r--r--test/http_pcap/http_over_tcp_keepalive.pcapbin0 -> 20213 bytes
-rw-r--r--test/http_pcap/http_over_tls.pcapbin0 -> 232820 bytes
-rw-r--r--test/http_pcap/http_post_multipart_form_data.pcapbin0 -> 4824 bytes
-rw-r--r--test/http_pcap/http_req_1byte_sliding_window.pcapbin0 -> 31724 bytes
-rw-r--r--test/http_pcap/http_res_1byte_sliding_window.pcapbin0 -> 55449 bytes
-rw-r--r--test/http_pcap/http_res_gzip.pcapbin0 -> 1707 bytes
-rw-r--r--test/http_pcap/http_trans_pipeline.pcapbin0 -> 6832 bytes
-rw-r--r--test/http_pcap/http_tunnel_for_pop3.pcapbin0 -> 3673 bytes
-rw-r--r--test/http_pcap/http_upgrade_http2.pcapbin0 -> 1617 bytes
-rw-r--r--test/http_pcap/http_upgrade_websocket.pcapbin0 -> 7851 bytes
-rw-r--r--test/http_pcap/http_url_test_with_host.pcapbin0 -> 1362 bytes
-rw-r--r--test/http_pcap/http_url_test_without_host.pcapbin0 -> 1624 bytes
-rw-r--r--test/http_pcap/non_http.pcapbin0 -> 27850 bytes
-rw-r--r--test/md5.c356
-rw-r--r--test/md5.h70
-rw-r--r--test/test_env/conflist.inf9
-rw-r--r--test/test_env/sapp4.el8.x86_64.rpmbin0 -> 1052968 bytes
-rw-r--r--test/test_env/spec.toml11
-rw-r--r--test/test_env/start_loader.inf17
-rw-r--r--test/test_env/tsg_l7_protocol.conf57
-rw-r--r--test/test_result_json/http_6over4_single_trans.json33
-rw-r--r--test/test_result_json/http_chunked_res_gzip.json41
-rw-r--r--test/test_result_json/http_connect_flood.json686
-rw-r--r--test/test_result_json/http_get_encoded_uri.json71
-rw-r--r--test/test_result_json/http_get_long_cookie.json24
-rw-r--r--test/test_result_json/http_get_malformed.json28
-rw-r--r--test/test_result_json/http_get_multi_trans.json139
-rw-r--r--test/test_result_json/http_get_req_pipeline.json67
-rw-r--r--test/test_result_json/http_get_single_trans.json29
-rw-r--r--test/test_result_json/http_hdr_truncated_after_kv.json285
-rw-r--r--test/test_result_json/http_hdr_truncated_in_kv.json53
-rw-r--r--test/test_result_json/http_hdr_value_empty.json35
-rw-r--r--test/test_result_json/http_hdrs_exceed_maximum.json46
-rw-r--r--test/test_result_json/http_multi_parse_error.json35
-rw-r--r--test/test_result_json/http_no_content_length.json45
-rw-r--r--test/test_result_json/http_over_pppoe.json35
-rw-r--r--test/test_result_json/http_over_tcp_keepalive.json40
-rw-r--r--test/test_result_json/http_over_tls.json2
-rw-r--r--test/test_result_json/http_post_multipart_form_data.json101
-rw-r--r--test/test_result_json/http_req_1byte_sliding_window.json34
-rw-r--r--test/test_result_json/http_res_1byte_sliding_window.json34
-rw-r--r--test/test_result_json/http_res_gzip.json44
-rw-r--r--test/test_result_json/http_trans_pipeline.json368
-rw-r--r--test/test_result_json/http_tunnel_for_pop3.json33
-rw-r--r--test/test_result_json/http_upgrade_http2.json42
-rw-r--r--test/test_result_json/http_upgrade_websocket.json42
-rw-r--r--test/test_result_json/http_url_test_with_host.json35
-rw-r--r--test/test_result_json/http_url_test_without_host.json29
-rw-r--r--test/test_result_json/non_http.json1
-rw-r--r--vendor/.gitkeep0
-rw-r--r--vendor/CMakeLists.txt53
-rw-r--r--vendor/googletest-release-1.8.0.tar.gzbin0 -> 1281617 bytes
-rw-r--r--vendor/libcjson_v1.7.17.tar.gzbin0 -> 354666 bytes
-rw-r--r--vendor/llhttp-release-v9.1.3.tar.gzbin0 -> 37771 bytes
125 files changed, 16562 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fe44c80
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.idea/
+.vscode
+build/
+build7
+build8
+cmake-build*
+.VSCodeCounter
+.cache
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..010d649
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,241 @@
+variables:
+ GIT_STRATEGY: "clone"
+ BUILD_PADDING_PREFIX: /tmp/padding_for_CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX_PREFIX_PREFIX_PREFIX_PREFIX_PREFIX/
+ BUILD_IMAGE_CENTOS7: "git.mesalab.cn:7443/mesa_platform/build-env:master"
+ BUILD_IMAGE_CENTOS8: "git.mesalab.cn:7443/mesa_platform/build-env:rockylinux"
+ INSTALL_DEPENDENCY_LIBRARY: sapp-devel framework_env libMESA_prof_load-devel
+ libMESA_htable-devel libMESA_jump_layer libMESA_jump_layer-devel
+ libMESA_handle_logger-devel libMESA_field_stat2-devel
+ libfieldstat3-devel libfieldstat4-devel libbreakpad_mini-devel
+ zlib-devel brotli brotli-devel stellar-c-devel
+ SYMBOL_TARGET: http_decoder
+ TEST_NAME: gtest_http_decoder
+ INSTALL_PREFIX: "/opt/tsg/"
+
+stages:
+- cppcheck
+- build
+- test
+- upload
+
+.everything_before_script: &everything_before_script
+ - mkdir -p $BUILD_PADDING_PREFIX/$CI_PROJECT_NAMESPACE/
+ - ln -s $CI_PROJECT_DIR $BUILD_PADDING_PREFIX/$CI_PROJECT_PATH
+ - cd $BUILD_PADDING_PREFIX/$CI_PROJECT_PATH
+ - chmod +x ./ci/travis.sh
+ - yum makecache #--disablerepo="*" --enablerepo="framework,platform,protocol,stellar"
+ - yum install -y $INSTALL_DEPENDENCY_LIBRARY
+ - source /etc/profile.d/framework.sh
+###############################################################################
+# cppcheck
+###############################################################################
+.cppcheck_script:
+ variables:
+ BUILD_TYPE: Debug
+ stage: cppcheck
+ script:
+ - mkdir build; cd build; cmake3 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..
+ - >
+ cppcheck --project=compile_commands.json
+ --enable=all
+ --error-exitcode=1
+ --suppress=unusedFunction
+ --suppress=missingInclude
+ --suppress=uselessAssignmentPtrArg
+ --suppress=unmatchedSuppression
+ --suppress=variableScope
+ --suppress=unreadVariable
+ --suppress=cstyleCast
+ --suppress=memleakOnRealloc
+ --suppress=constParameter
+ --suppress=uselessAssignmentArg
+ --suppress=uninitvar
+ --suppress=unusedStructMember
+ tags:
+ - share
+
+run_cppcheck_for_centos7:
+ extends: .cppcheck_script
+ image: $BUILD_IMAGE_CENTOS7
+
+run_cppcheck_for_centos8:
+ extends: .cppcheck_script
+ image: $BUILD_IMAGE_CENTOS8
+
+###############################################################################
+# build
+###############################################################################
+
+.build_before_script:
+ before_script: *everything_before_script
+ script:
+ - ./ci/travis.sh
+ variables:
+ BUILD_TEST: "ON"
+ BUILD_TYPE: Debug
+ tags:
+ - share
+
+.build_by_travis_for_centos7:
+ extends: .build_before_script
+ stage: build
+ image: $BUILD_IMAGE_CENTOS7
+
+
+develop_build_for_centos7:
+ extends: .build_by_travis_for_centos7
+ variables:
+ BUILD_TYPE: RelWithDebInfo
+ artifacts:
+ name: "$SYMBOL_TARGET-$CI_COMMIT_REF_NAME-debug"
+ paths:
+ - build/*
+ except:
+ - tags
+
+release_build_debug_for_centos7:
+ extends: .build_by_travis_for_centos7
+ variables:
+ BUILD_TYPE: Debug
+ PACKAGE: 1
+ ASAN_OPTION: "ADDRESS"
+ script:
+ - source /opt/rh/devtoolset-7/enable || true
+ - ./ci/travis.sh
+ artifacts:
+ name: "$SYMBOL_TARGET-$CI_COMMIT_REF_NAME-release"
+ paths:
+ - build/*
+ only:
+ - tags
+
+release_build_for_centos7:
+ extends: .build_by_travis_for_centos7
+ variables:
+ BUILD_TYPE: RelWithDebInfo
+ PACKAGE: 1
+ artifacts:
+ name: "$SYMBOL_TARGET-$CI_COMMIT_REF_NAME-release"
+ paths:
+ - build/*
+ only:
+ - tags
+
+.build_by_travis_for_centos8:
+ stage: build
+ image: $BUILD_IMAGE_CENTOS8
+ extends: .build_before_script
+
+develop_build_for_centos8:
+ extends: .build_by_travis_for_centos8
+ variables:
+ BUILD_TYPE: RelWithDebInfo
+ artifacts:
+ name: "$SYMBOL_TARGET-$CI_COMMIT_REF_NAME-debug"
+ paths:
+ - build/*
+ except:
+ - tags
+
+release_build_debug_for_centos8:
+ extends: .build_by_travis_for_centos8
+ variables:
+ BUILD_TYPE: Debug
+ PACKAGE: 1
+ ASAN_OPTION: "ADDRESS"
+ artifacts:
+ name: "$SYMBOL_TARGET-$CI_COMMIT_REF_NAME-release"
+ paths:
+ - build/*
+ only:
+ - tags
+
+release_build_for_centos8:
+ extends: .build_by_travis_for_centos8
+ variables:
+ BUILD_TYPE: RelWithDebInfo
+ PACKAGE: 1
+ artifacts:
+ name: "$SYMBOL_TARGET-$CI_COMMIT_REF_NAME-release"
+ paths:
+ - build/*
+ only:
+ - tags
+
+###############################################################################
+# test
+###############################################################################
+test_in_centos7:
+ stage: test
+ image: $BUILD_IMAGE_CENTOS7
+ allow_failure: false
+ script:
+ - *everything_before_script
+ - ls -l /opt/MESA/lib && echo "/opt/MESA/lib" >> /etc/ld.so.conf
+ - cd build; make test
+ dependencies:
+ - develop_build_for_centos7
+ - release_build_for_centos7
+ tags:
+ - share
+
+test_in_centos8:
+ stage: test
+ image: $BUILD_IMAGE_CENTOS8
+ allow_failure: false
+ script:
+ - *everything_before_script
+ - ls -l /opt/MESA/lib && echo "/opt/MESA/lib" >> /etc/ld.so.conf
+ - cd build; make test
+ dependencies:
+ - develop_build_for_centos8
+ - release_build_for_centos8
+ tags:
+ - share
+
+###############################################################################
+# upload
+###############################################################################
+.define_before_upload_centos7:
+ stage: upload
+ image: $BUILD_IMAGE_CENTOS7
+ before_script:
+ - pwd; ls -l ; cd build ; ls -l
+ - cp /root/rpm_upload_tools.py ./
+ variables:
+ PULP3_REPO_NAME: protocol-stable-x86_64.el7
+ PULP3_DIST_NAME: protocol-stable-x86_64.el7
+ only:
+ - tags
+ tags:
+ - share
+
+rpm_upload_for_centos7:
+ extends: .define_before_upload_centos7
+ dependencies:
+ - release_build_debug_for_centos7
+ - release_build_for_centos7
+ script:
+ - python3 rpm_upload_tools.py $PULP3_REPO_NAME $PULP3_DIST_NAME *.rpm
+
+.define_before_upload_centos8:
+ stage: upload
+ image: $BUILD_IMAGE_CENTOS8
+ before_script:
+ - pwd; ls -l ; cd build ; ls -l
+ - cp /root/rpm_upload_tools.py ./
+ variables:
+ PULP3_REPO_NAME: protocol-stable-x86_64.el8
+ PULP3_DIST_NAME: protocol-stable-x86_64.el8
+ only:
+ - tags
+ tags:
+ - share
+
+rpm_upload_for_centos8:
+ extends: .define_before_upload_centos8
+ dependencies:
+ - release_build_debug_for_centos8
+ - release_build_for_centos8
+ script:
+ - python3 rpm_upload_tools.py $PULP3_REPO_NAME $PULP3_DIST_NAME *.rpm
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..a3e100c
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,92 @@
+cmake_minimum_required(VERSION 3.12)
+set(lib_name http_decoder)
+
+project (${lib_name})
+
+
+set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
+
+include(Version)
+
+add_definitions(-D_GNU_SOURCE)
+add_definitions(-D__USE_MISC)
+add_definitions(-D__FAVOR_BSD)
+add_definitions(-D__USE_BSD)
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_C_STANDARD 11)
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE RelWithDebInfo)
+endif()
+
+if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set (CMAKE_INSTALL_PREFIX "/opt/tsg/" CACHE PATH "default install path" FORCE)
+endif()
+
+LINK_DIRECTORIES(/opt/MESA/lib /usr/lib64)
+
+find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck)
+if (CMAKE_CXX_CPPCHECK)
+ list(
+ APPEND CMAKE_CXX_CPPCHECK
+ "--enable=all"
+ "--error-exitcode=1"
+ "--suppress=unusedFunction"
+ "--suppress=missingInclude"
+ "--suppress=uselessAssignmentPtrArg"
+ "--suppress=unmatchedSuppression"
+ "--suppress=variableScope"
+ "--suppress=unreadVariable"
+ "--suppress=cstyleCast"
+ "--suppress=memleakOnRealloc"
+ "--suppress=constParameter"
+ "--suppress=uselessAssignmentArg"
+ "--suppress=uninitvar"
+ "--suppress=unusedStructMember"
+ "--suppress=unreachableCode"
+ "--suppress=internalAstError"
+ "--suppress=nullPointerRedundantCheck"
+ "--suppress=ctunullpointer"
+ "--suppress=redundantAssignment"
+ "--suppress=duplicateValueTernary"
+ )
+ set(CMAKE_C_CPPCHECK ${CMAKE_CXX_CPPCHECK})
+else()
+ message(FATAL_ERROR "Could not find the program cppcheck.")
+endif()
+
+#ASAN option
+set(ASAN_OPTION "OFF" CACHE STRING
+ " set asan type chosen by the user, using OFF as default")
+set_property(CACHE ASAN_OPTION PROPERTY STRINGS OFF ADDRESS THREAD)
+message(STATUS "ASAN_OPTION='${ASAN_OPTION}'")
+
+if(ASAN_OPTION STREQUAL "ADDRESS")
+ set(CMAKE_C_FLAGS "${CMAKADDRESS} -g -DCMAKE_BUILD_TYPE=Debug -fsanitize=address -fno-omit-frame-pointer")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -DCMAKE_BUILD_TYPE=Debug -fsanitize=address -fno-omit-frame-pointer")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan")
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan")
+ add_definitions(-DASAN_ENABLED=1)
+elseif(ASAN_OPTION STREQUAL "THREAD")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -DCMAKE_BUILD_TYPE=Debug -fsanitize=thread -fno-omit-frame-pointer")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -DCMAKE_BUILD_TYPE=Debug -fsanitize=thread -fno-omit-frame-pointer")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan")
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lasan")
+ add_definitions(-DASAN_ENABLED=1)
+endif()
+
+include_directories(${CMAKE_SOURCE_DIR})
+include_directories(${CMAKE_SOURCE_DIR}/include)
+
+add_subdirectory(vendor)
+add_subdirectory(src)
+
+enable_testing()
+add_subdirectory(test)
+
+set(CPACK_RPM_LIBRARIES_USER_FILELIST "%config(noreplace) ${CMAKE_INSTALL_PREFIX}/etc/http/http_decoder.toml")
+
+install(FILES conf/http_decoder.toml DESTINATION ${CMAKE_INSTALL_PREFIX}/etc/http COMPONENT PROFILE)
+install(FILES include/http_decoder.h DESTINATION ${CMAKE_INSTALL_PREFIX}/framework/include/http_decoder COMPONENT Headers)
+
+include(Package) \ No newline at end of file
diff --git a/autorevision.sh b/autorevision.sh
new file mode 100644
index 0000000..80d0712
--- /dev/null
+++ b/autorevision.sh
@@ -0,0 +1,1260 @@
+#!/bin/sh
+
+# Copyright (c) 2012 - 2016 dak180 and contributors. See
+# https://opensource.org/licenses/mit-license.php or the included
+# COPYING.md for licence terms.
+#
+# autorevision - extracts metadata about the head version from your
+# repository.
+
+# Usage message.
+arUsage() {
+ cat >"/dev/stderr" <<EOF
+usage: autorevision {-t output-type | -s symbol} [-o cache-file [-f] ] [-V]
+ Options include:
+ -t output-type = specify output type
+ -s symbol = specify symbol output
+ -o cache-file = specify cache file location
+ -f = force the use of cache data
+ -U = check for untracked files in svn
+ -V = emit version and exit
+ -? = help message
+
+The following are valid output types:
+ clojure = clojure file
+ c = C/C++ file
+ h = Header for use with c/c++
+ hpp = Alternate C++ header strings with namespace
+ ini = INI file
+ java = Java file
+ javaprop = Java properties file
+ js = javascript file
+ json = JSON file
+ lua = Lua file
+ m4 = m4 file
+ matlab = matlab file
+ octave = octave file
+ php = PHP file
+ pl = Perl file
+ py = Python file
+ rpm = rpm file
+ scheme = scheme file
+ sh = Bash sytax
+ swift = Swift file
+ tex = (La)TeX file
+ xcode = Header useful for populating info.plist files
+ cmake = CMake file
+
+
+The following are valid symbols:
+ VCS_TYPE
+ VCS_BASENAME
+ VCS_UUID
+ VCS_NUM
+ VCS_DATE
+ VCS_BRANCH
+ VCS_TAG
+ VCS_TICK
+ VCS_EXTRA
+ VCS_FULL_HASH
+ VCS_SHORT_HASH
+ VCS_WC_MODIFIED
+ VCS_ACTION_STAMP
+EOF
+ exit 1
+}
+
+# Config
+ARVERSION="&&ARVERSION&&"
+TARGETFILE="/dev/stdout"
+while getopts ":t:o:s:VfU" OPTION; do
+ case "${OPTION}" in
+ t)
+ AFILETYPE="${OPTARG}"
+ ;;
+ o)
+ CACHEFILE="${OPTARG}"
+ ;;
+ f)
+ CACHEFORCE="1"
+ ;;
+ s)
+ VAROUT="${OPTARG}"
+ ;;
+ U)
+ UNTRACKEDFILES="1"
+ ;;
+ V)
+ echo "autorevision ${ARVERSION}"
+ exit 0
+ ;;
+ ?)
+ # If an unknown flag is used (or -?):
+ arUsage
+ ;;
+ esac
+done
+
+if [ ! -z "${VAROUT}" ] && [ ! -z "${AFILETYPE}" ]; then
+ # If both -s and -t are specified:
+ echo "error: Improper argument combination." 1>&2
+ exit 1
+elif [ -z "${VAROUT}" ] && [ -z "${AFILETYPE}" ]; then
+ # If neither -s or -t are specified:
+ arUsage
+elif [ -z "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then
+ # If -f is specified without -o:
+ arUsage
+elif [ ! -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then
+ # If we are forced to use the cache but it does not exist.
+ echo "error: Cache forced but no cache found." 1>&2
+ exit 1
+fi
+
+# Make sure that the path we are given is one we can source
+# (dash, we are looking at you).
+if [ ! -z "${CACHEFILE}" ] && ! echo "${CACHEFILE}" | grep -q '^\.*/'; then
+ CACHEFILE="./${CACHEFILE}"
+fi
+
+GENERATED_HEADER="Generated by autorevision - do not hand-hack!"
+
+# Functions to extract data from different repo types.
+# For git repos
+# shellcheck disable=SC2039,SC2164,SC2155
+gitRepo() {
+ local oldPath="${PWD}"
+
+ cd "$(git rev-parse --show-toplevel)"
+
+ VCS_TYPE="git"
+
+ VCS_BASENAME="$(basename "${PWD}")"
+
+ VCS_UUID="$(git rev-list --max-parents=0 --date-order --reverse HEAD 2>/dev/null | sed -n 1p)"
+ if [ -z "${VCS_UUID}" ]; then
+ VCS_UUID="$(git rev-list --topo-order HEAD | tail -n 1)"
+ fi
+
+ # Is the working copy clean?
+ test -z "$(git status --untracked-files=normal --porcelain)"
+ VCS_WC_MODIFIED="${?}"
+
+ # Enumeration of changesets
+ VCS_NUM="$(git rev-list --count HEAD 2>/dev/null)"
+ if [ -z "${VCS_NUM}" ]; then
+ echo "warning: Counting the number of revisions may be slower due to an outdated git version less than 1.7.2.3. If something breaks, please update it." 1>&2
+ VCS_NUM="$(git rev-list HEAD | wc -l)"
+ fi
+
+ # This may be a git-svn remote. If so, report the Subversion revision.
+ if [ -z "$(git config svn-remote.svn.url 2>/dev/null)" ]; then
+ # The full revision hash
+ VCS_FULL_HASH="$(git rev-parse HEAD)"
+
+ # The short hash
+ VCS_SHORT_HASH="$(echo "${VCS_FULL_HASH}" | cut -b 1-7)"
+ else
+ # The git-svn revision number
+ VCS_FULL_HASH="$(git svn find-rev HEAD)"
+ VCS_SHORT_HASH="${VCS_FULL_HASH}"
+ fi
+
+ # Current branch
+ VCS_BRANCH="$(git rev-parse --symbolic-full-name --verify "$(git name-rev --name-only --no-undefined HEAD 2>/dev/null)" 2>/dev/null | sed -e 's:refs/heads/::' | sed -e 's:refs/::')"
+
+ # Cache the description
+ local DESCRIPTION="$(git describe --long --tags 2>/dev/null)"
+
+ # Current or last tag ancestor (empty if no tags)
+ VCS_TAG="$(echo "${DESCRIPTION}" | sed -e "s:-g${VCS_SHORT_HASH}\$::" -e 's:-[0-9]*$::')"
+
+ # Distance to last tag or an alias of VCS_NUM if there is no tag
+ if [ ! -z "${DESCRIPTION}" ]; then
+ VCS_TICK="$(echo "${DESCRIPTION}" | sed -e "s:${VCS_TAG}-::" -e "s:-g${VCS_SHORT_HASH}::")"
+ else
+ VCS_TICK="${VCS_NUM}"
+ fi
+
+ # Date of the current commit
+ VCS_DATE="$(TZ=UTC git show -s --date=iso-strict-local --pretty=format:%ad | sed -e 's|+00:00|Z|')"
+ if [ -z "${VCS_DATE}" ]; then
+ echo "warning: Action stamps require git version 2.7+." 1>&2
+ VCS_DATE="$(git log -1 --pretty=format:%ci | sed -e 's: :T:' -e 's: ::' -e 's|+00:00|Z|')"
+ local ASdis="1"
+ fi
+
+ # Action Stamp
+ if [ -z "${ASdis}" ]; then
+ VCS_ACTION_STAMP="${VCS_DATE}!$(git show -s --pretty=format:%cE)"
+ else
+ VCS_ACTION_STAMP=""
+ fi
+
+ cd "${oldPath}"
+}
+
+# For hg repos
+# shellcheck disable=SC2039,SC2164
+hgRepo() {
+ local oldPath="${PWD}"
+
+ cd "$(hg root)"
+
+ VCS_TYPE="hg"
+
+ VCS_BASENAME="$(basename "${PWD}")"
+
+ VCS_UUID="$(hg log -r "0" -l 1 --template '{node}\n')"
+
+ # Is the working copy clean?
+ test -z "$(hg status -duram)"
+ VCS_WC_MODIFIED="${?}"
+
+ # Enumeration of changesets
+ VCS_NUM="$(hg id -n | tr -d '+')"
+
+ # The full revision hash
+ VCS_FULL_HASH="$(hg log -r "${VCS_NUM}" -l 1 --template '{node}\n')"
+
+ # The short hash
+ VCS_SHORT_HASH="$(hg id -i | tr -d '+')"
+
+ # Current bookmark (bookmarks are roughly equivalent to git's branches)
+ # or branch if no bookmark
+ VCS_BRANCH="$(hg id -B | cut -d ' ' -f 1)"
+ # Fall back to the branch if there are no bookmarks
+ if [ -z "${VCS_BRANCH}" ]; then
+ VCS_BRANCH="$(hg id -b)"
+ fi
+
+ # Current or last tag ancestor (excluding auto tags, empty if no tags)
+ VCS_TAG="$(hg log -r "${VCS_NUM}" -l 1 --template '{latesttag}\n' 2>/dev/null | sed -e 's:qtip::' -e 's:tip::' -e 's:qbase::' -e 's:qparent::' -e "s:$(hg --config 'extensions.color=' --config 'extensions.mq=' --color never qtop 2>/dev/null)::" | cut -d ' ' -f 1)"
+
+ # Distance to last tag or an alias of VCS_NUM if there is no tag
+ if [ ! -z "${VCS_TAG}" ]; then
+ VCS_TICK="$(hg log -r "${VCS_NUM}" -l 1 --template '{latesttagdistance}\n' 2>/dev/null)"
+ else
+ VCS_TICK="${VCS_NUM}"
+ fi
+
+ # Date of the current commit
+ VCS_DATE="$(hg log -r "${VCS_NUM}" -l 1 --template '{date|isodatesec}\n' 2>/dev/null | sed -e 's: :T:' -e 's: ::' -e 's|+00:00|Z|')"
+
+ # Action Stamp
+ VCS_ACTION_STAMP="$(TZ=UTC hg log -r "${VCS_NUM}" -l 1 --template '{date|localdate|rfc3339date}\n' 2>/dev/null | sed -e 's|+00:00|Z|')!$(hg log -r "${VCS_NUM}" -l 1 --template '{author|email}\n' 2>/dev/null)"
+
+ cd "${oldPath}"
+}
+
+# For bzr repos
+# shellcheck disable=SC2039,SC2164
+bzrRepo() {
+ local oldPath="${PWD}"
+
+ cd "$(bzr root)"
+
+ VCS_TYPE="bzr"
+
+ VCS_BASENAME="$(basename "${PWD}")"
+
+ # Currently unimplemented because more investigation is needed.
+ VCS_UUID=""
+
+ # Is the working copy clean?
+ bzr version-info --custom --template='{clean}\n' | grep -q '1'
+ VCS_WC_MODIFIED="${?}"
+
+ # Enumeration of changesets
+ VCS_NUM="$(bzr revno)"
+
+ # The full revision hash
+ VCS_FULL_HASH="$(bzr version-info --custom --template='{revision_id}\n')"
+
+ # The short hash
+ VCS_SHORT_HASH="${VCS_NUM}"
+
+ # Nick of the current branch
+ VCS_BRANCH="$(bzr nick)"
+
+ # Current or last tag ancestor (excluding auto tags, empty if no tags)
+ VCS_TAG="$(bzr tags --sort=time | sed '/?$/d' | tail -n1 | cut -d ' ' -f1)"
+
+ # Distance to last tag or an alias of VCS_NUM if there is no tag
+ if [ ! -z "${VCS_TAG}" ]; then
+ VCS_TICK="$(bzr log --line -r "tag:${VCS_TAG}.." | tail -n +2 | wc -l | sed -e 's:^ *::')"
+ else
+ VCS_TICK="${VCS_NUM}"
+ fi
+
+ # Date of the current commit
+ VCS_DATE="$(bzr version-info --custom --template='{date}\n' | sed -e 's: :T:' -e 's: ::')"
+
+ # Action Stamp
+ # Currently unimplemented because more investigation is needed.
+ VCS_ACTION_STAMP=""
+
+ cd "${oldPath}"
+}
+
+# For svn repos
+# shellcheck disable=SC2039,SC2164,SC2155
+svnRepo() {
+ local oldPath="${PWD}"
+
+ VCS_TYPE="svn"
+
+ case "${PWD}" in
+ /*trunk* | /*branches* | /*tags*)
+ local fn="${PWD}"
+ while [ "$(basename "${fn}")" != 'trunk' ] && [ "$(basename "${fn}")" != 'branches' ] && [ "$(basename "${fn}")" != 'tags' ] && [ "$(basename "${fn}")" != '/' ]; do
+ local fn="$(dirname "${fn}")"
+ done
+ local fn="$(dirname "${fn}")"
+ if [ "${fn}" = '/' ]; then
+ VCS_BASENAME="$(basename "${PWD}")"
+ else
+ VCS_BASENAME="$(basename "${fn}")"
+ fi
+ ;;
+ *) VCS_BASENAME="$(basename "${PWD}")" ;;
+ esac
+
+ VCS_UUID="$(svn info --xml | sed -n -e 's:<uuid>::' -e 's:</uuid>::p')"
+
+ # Cache svnversion output
+ local SVNVERSION="$(svnversion)"
+
+ # Is the working copy clean?
+ echo "${SVNVERSION}" | grep -q "M"
+ case "${?}" in
+ 0)
+ VCS_WC_MODIFIED="1"
+ ;;
+ 1)
+ if [ ! -z "${UNTRACKEDFILES}" ]; then
+ # `svnversion` does not detect untracked files and `svn status` is really slow, so only run it if we really have to.
+ if [ -z "$(svn status)" ]; then
+ VCS_WC_MODIFIED="0"
+ else
+ VCS_WC_MODIFIED="1"
+ fi
+ else
+ VCS_WC_MODIFIED="0"
+ fi
+ ;;
+ esac
+
+ # Enumeration of changesets
+ VCS_NUM="$(echo "${SVNVERSION}" | cut -d : -f 1 | sed -e 's:M::' -e 's:S::' -e 's:P::')"
+
+ # The full revision hash
+ VCS_FULL_HASH="${SVNVERSION}"
+
+ # The short hash
+ VCS_SHORT_HASH="${VCS_NUM}"
+
+ # Current branch
+ case "${PWD}" in
+ /*trunk* | /*branches* | /*tags*)
+ local lastbase=""
+ local fn="${PWD}"
+ while :; do
+ base="$(basename "${fn}")"
+ if [ "${base}" = 'trunk' ]; then
+ VCS_BRANCH='trunk'
+ break
+ elif [ "${base}" = 'branches' ] || [ "${base}" = 'tags' ]; then
+ VCS_BRANCH="${lastbase}"
+ break
+ elif [ "${base}" = '/' ]; then
+ VCS_BRANCH=""
+ break
+ fi
+ local lastbase="${base}"
+ local fn="$(dirname "${fn}")"
+ done
+ ;;
+ *) VCS_BRANCH="" ;;
+ esac
+
+ # Current or last tag ancestor (empty if no tags). But "current
+ # tag" can't be extracted reliably because Subversion doesn't
+ # have tags the way other VCSes do.
+ VCS_TAG=""
+ VCS_TICK=""
+
+ # Date of the current commit
+ VCS_DATE="$(svn info --xml | sed -n -e 's:<date>::' -e 's:</date>::p')"
+
+ # Action Stamp
+ VCS_ACTION_STAMP="${VCS_DATE}!$(svn log --xml -l 1 -r "${VCS_SHORT_HASH}" | sed -n -e 's:<author>::' -e 's:</author>::p')"
+
+ cd "${oldPath}"
+}
+
+# Functions to output data in different formats.
+# For bash output
+shOutput() {
+ cat >"${TARGETFILE}" <<EOF
+# ${GENERATED_HEADER}
+
+VCS_TYPE="${VCS_TYPE}"
+VCS_BASENAME="${VCS_BASENAME}"
+VCS_UUID="${VCS_UUID}"
+VCS_NUM="${VCS_NUM}"
+VCS_DATE="${VCS_DATE}"
+VCS_BRANCH="${VCS_BRANCH}"
+VCS_TAG="${VCS_TAG}"
+VCS_TICK="${VCS_TICK}"
+VCS_EXTRA="${VCS_EXTRA}"
+
+VCS_ACTION_STAMP="${VCS_ACTION_STAMP}"
+VCS_FULL_HASH="${VCS_FULL_HASH}"
+VCS_SHORT_HASH="${VCS_SHORT_HASH}"
+
+VCS_WC_MODIFIED="${VCS_WC_MODIFIED}"
+
+# end
+EOF
+}
+
+# For source C output
+cOutput() {
+ cat >"${TARGETFILE}" <<EOF
+/* ${GENERATED_HEADER} */
+
+const char *VCS_TYPE = "${VCS_TYPE}";
+const char *VCS_BASENAME = "${VCS_BASENAME}";
+const char *VCS_UUID = "${VCS_UUID}";
+const int VCS_NUM = ${VCS_NUM};
+const char *VCS_DATE = "${VCS_DATE}";
+const char *VCS_BRANCH = "${VCS_BRANCH}";
+const char *VCS_TAG = "${VCS_TAG}";
+const int VCS_TICK = ${VCS_TICK};
+const char *VCS_EXTRA = "${VCS_EXTRA}";
+
+const char *VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}";
+const char *VCS_FULL_HASH = "${VCS_FULL_HASH}";
+const char *VCS_SHORT_HASH = "${VCS_SHORT_HASH}";
+
+const int VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+
+/* end */
+EOF
+}
+
+# For header output
+hOutput() {
+ cat >"${TARGETFILE}" <<EOF
+/* ${GENERATED_HEADER} */
+#ifndef AUTOREVISION_H
+#define AUTOREVISION_H
+
+#define VCS_TYPE "${VCS_TYPE}"
+#define VCS_BASENAME "${VCS_BASENAME}"
+#define VCS_UUID "${VCS_UUID}"
+#define VCS_NUM ${VCS_NUM}
+#define VCS_DATE "${VCS_DATE}"
+#define VCS_BRANCH "${VCS_BRANCH}"
+#define VCS_TAG "${VCS_TAG}"
+#define VCS_TICK ${VCS_TICK}
+#define VCS_EXTRA "${VCS_EXTRA}"
+
+#define VCS_ACTION_STAMP "${VCS_ACTION_STAMP}"
+#define VCS_FULL_HASH "${VCS_FULL_HASH}"
+#define VCS_SHORT_HASH "${VCS_SHORT_HASH}"
+
+#define VCS_WC_MODIFIED ${VCS_WC_MODIFIED}
+
+#endif
+
+/* end */
+EOF
+}
+
+# A header output for use with xcode to populate info.plist strings
+xcodeOutput() {
+ cat >"${TARGETFILE}" <<EOF
+/* ${GENERATED_HEADER} */
+#ifndef AUTOREVISION_H
+#define AUTOREVISION_H
+
+#define VCS_TYPE ${VCS_TYPE}
+#define VCS_BASENAME ${VCS_BASENAME}
+#define VCS_UUID ${VCS_UUID}
+#define VCS_NUM ${VCS_NUM}
+#define VCS_DATE ${VCS_DATE}
+#define VCS_BRANCH ${VCS_BRANCH}
+#define VCS_TAG ${VCS_TAG}
+#define VCS_TICK ${VCS_TICK}
+#define VCS_EXTRA ${VCS_EXTRA}
+
+#define VCS_ACTION_STAMP ${VCS_ACTION_STAMP}
+#define VCS_FULL_HASH ${VCS_FULL_HASH}
+#define VCS_SHORT_HASH ${VCS_SHORT_HASH}
+
+#define VCS_WC_MODIFIED ${VCS_WC_MODIFIED}
+
+#endif
+
+/* end */
+EOF
+}
+
+# For Swift output
+swiftOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ # For values that may not exist depending on the type of repo we
+ # have read from, set them to `nil` when they are empty.
+ if [ -z "${VCS_UUID}" ]; then
+ VCS_UUID="nil"
+ else
+ VCS_UUID="\"${VCS_UUID}\""
+ fi
+ if [ -z "${VCS_TAG}" ]; then
+ VCS_TAG="nil"
+ else
+ VCS_TAG="\"${VCS_TAG}\""
+ fi
+ : "${VCS_TICK:="nil"}"
+ if [ -z "${VCS_EXTRA}" ]; then
+ VCS_EXTRA="nil"
+ else
+ VCS_EXTRA="\"${VCS_EXTRA}\""
+ fi
+ if [ -z "${VCS_ACTION_STAMP}" ]; then
+ VCS_ACTION_STAMP="nil"
+ else
+ VCS_ACTION_STAMP="\"${VCS_ACTION_STAMP}\""
+ fi
+ cat >"${TARGETFILE}" <<EOF
+/* ${GENERATED_HEADER} */
+
+let VCS_TYPE = "${VCS_TYPE}"
+let VCS_BASENAME = "${VCS_BASENAME}"
+let VCS_UUID: String? = ${VCS_UUID}
+let VCS_NUM: Int = ${VCS_NUM}
+let VCS_DATE = "${VCS_DATE}"
+let VCS_BRANCH: String = "${VCS_BRANCH}"
+let VCS_TAG: String? = ${VCS_TAG}
+let VCS_TICK: Int? = ${VCS_TICK}
+let VCS_EXTRA: String? = ${VCS_EXTRA}
+
+let VCS_ACTION_STAMP: String? = ${VCS_ACTION_STAMP}
+let VCS_FULL_HASH: String = "${VCS_FULL_HASH}"
+let VCS_SHORT_HASH: String = "${VCS_SHORT_HASH}"
+
+let VCS_WC_MODIFIED: Bool = ${VCS_WC_MODIFIED}
+
+/* end */
+EOF
+}
+
+# For Python output
+pyOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="False" ;;
+ 1) VCS_WC_MODIFIED="True" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+# ${GENERATED_HEADER}
+
+VCS_TYPE = "${VCS_TYPE}"
+VCS_BASENAME = "${VCS_BASENAME}"
+VCS_UUID = "${VCS_UUID}"
+VCS_NUM = ${VCS_NUM}
+VCS_DATE = "${VCS_DATE}"
+VCS_BRANCH = "${VCS_BRANCH}"
+VCS_TAG = "${VCS_TAG}"
+VCS_TICK = ${VCS_TICK}
+VCS_EXTRA = "${VCS_EXTRA}"
+
+VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"
+VCS_FULL_HASH = "${VCS_FULL_HASH}"
+VCS_SHORT_HASH = "${VCS_SHORT_HASH}"
+
+VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}
+
+# end
+EOF
+}
+
+# For Perl output
+plOutput() {
+ cat <<EOF
+# ${GENERATED_HEADER}
+
+\$VCS_TYPE = '${VCS_TYPE}';
+\$VCS_BASENAME = '${VCS_BASENAME}';
+\$VCS_UUID = '${VCS_UUID}';
+\$VCS_NUM = ${VCS_NUM};
+\$VCS_DATE = '${VCS_DATE}';
+\$VCS_BRANCH = '${VCS_BRANCH}';
+\$VCS_TAG = '${VCS_TAG}';
+\$VCS_TICK = ${VCS_TICK};
+\$VCS_EXTRA = '${VCS_EXTRA}';
+
+\$VCS_ACTION_STAMP = '${VCS_ACTION_STAMP}';
+\$VCS_FULL_HASH = '${VCS_FULL_HASH}';
+\$VCS_SHORT_HASH = '${VCS_SHORT_HASH}';
+
+\$VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+
+# end
+1;
+EOF
+}
+
+# For lua output
+luaOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+-- ${GENERATED_HEADER}
+
+VCS_TYPE = "${VCS_TYPE}"
+VCS_BASENAME = "${VCS_BASENAME}"
+VCS_UUID = "${VCS_UUID}"
+VCS_NUM = ${VCS_NUM}
+VCS_DATE = "${VCS_DATE}"
+VCS_BRANCH = "${VCS_BRANCH}"
+VCS_TAG = "${VCS_TAG}"
+VCS_TICK = ${VCS_TICK}
+VCS_EXTRA = "${VCS_EXTRA}"
+
+VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"
+VCS_FULL_HASH = "${VCS_FULL_HASH}"
+VCS_SHORT_HASH = "${VCS_SHORT_HASH}"
+
+VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}
+
+-- end
+EOF
+}
+
+# For php output
+phpOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+<?php
+# ${GENERATED_HEADER}
+
+return array(
+ "VCS_TYPE" => "${VCS_TYPE}",
+ "VCS_BASENAME" => "${VCS_BASENAME}",
+ "VCS_UUID" => "${VCS_UUID}",
+ "VCS_NUM" => ${VCS_NUM},
+ "VCS_DATE" => "${VCS_DATE}",
+ "VCS_BRANCH" => "${VCS_BRANCH}",
+ "VCS_TAG" => "${VCS_TAG}",
+ "VCS_TICK" => ${VCS_TICK},
+ "VCS_EXTRA" => "${VCS_EXTRA}",
+ "VCS_ACTION_STAMP" => "${VCS_ACTION_STAMP}",
+ "VCS_FULL_HASH" => "${VCS_FULL_HASH}",
+ "VCS_SHORT_HASH" => "${VCS_SHORT_HASH}",
+ "VCS_WC_MODIFIED" => ${VCS_WC_MODIFIED}
+);
+
+# end
+?>
+EOF
+}
+
+# For ini output
+iniOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+; ${GENERATED_HEADER}
+[VCS]
+VCS_TYPE = "${VCS_TYPE}"
+VCS_BASENAME = "${VCS_BASENAME}"
+VCS_UUID = "${VCS_UUID}"
+VCS_NUM = ${VCS_NUM}
+VCS_DATE = "${VCS_DATE}"
+VCS_BRANCH = "${VCS_BRANCH}"
+VCS_TAG = "${VCS_TAG}"
+VCS_TICK = ${VCS_TICK}
+VCS_EXTRA = "${VCS_EXTRA}"
+VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}"
+VCS_FULL_HASH = "${VCS_FULL_HASH}"
+VCS_SHORT_HASH = "${VCS_SHORT_HASH}"
+VCS_WC_MODIFIED = ${VCS_WC_MODIFIED}
+; end
+EOF
+}
+
+# For javascript output
+jsOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 1) VCS_WC_MODIFIED="true" ;;
+ 0) VCS_WC_MODIFIED="false" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+/** ${GENERATED_HEADER} */
+
+var autorevision = {
+ VCS_TYPE: "${VCS_TYPE}",
+ VCS_BASENAME: "${VCS_BASENAME}",
+ VCS_UUID: "${VCS_UUID}",
+ VCS_NUM: ${VCS_NUM},
+ VCS_DATE: "${VCS_DATE}",
+ VCS_BRANCH: "${VCS_BRANCH}",
+ VCS_TAG: "${VCS_TAG}",
+ VCS_TICK: ${VCS_TICK},
+ VCS_EXTRA: "${VCS_EXTRA}",
+
+ VCS_ACTION_STAMP: "${VCS_ACTION_STAMP}",
+ VCS_FULL_HASH: "${VCS_FULL_HASH}",
+ VCS_SHORT_HASH: "${VCS_SHORT_HASH}",
+
+ VCS_WC_MODIFIED: ${VCS_WC_MODIFIED}
+};
+
+/** Node.js compatibility */
+if (typeof module !== 'undefined') {
+ module.exports = autorevision;
+}
+
+/** end */
+EOF
+}
+
+# For JSON output
+jsonOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 1) VCS_WC_MODIFIED="true" ;;
+ 0) VCS_WC_MODIFIED="false" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+{
+ "_comment": "${GENERATED_HEADER}",
+ "VCS_TYPE": "${VCS_TYPE}",
+ "VCS_BASENAME": "${VCS_BASENAME}",
+ "VCS_UUID": "${VCS_UUID}",
+ "VCS_NUM": ${VCS_NUM},
+ "VCS_DATE": "${VCS_DATE}",
+ "VCS_BRANCH":"${VCS_BRANCH}",
+ "VCS_TAG": "${VCS_TAG}",
+ "VCS_TICK": ${VCS_TICK},
+ "VCS_EXTRA": "${VCS_EXTRA}",
+
+ "VCS_ACTION_STAMP": "${VCS_ACTION_STAMP}",
+ "VCS_FULL_HASH": "${VCS_FULL_HASH}",
+ "VCS_SHORT_HASH": "${VCS_SHORT_HASH}",
+
+ "VCS_WC_MODIFIED": ${VCS_WC_MODIFIED}
+}
+EOF
+}
+
+# For Java output
+javaOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 1) VCS_WC_MODIFIED="true" ;;
+ 0) VCS_WC_MODIFIED="false" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+/* ${GENERATED_HEADER} */
+
+public class autorevision {
+ public static final String VCS_TYPE = "${VCS_TYPE}";
+ public static final String VCS_BASENAME = "${VCS_BASENAME}";
+ public static final String VCS_UUID = "${VCS_UUID}";
+ public static final long VCS_NUM = ${VCS_NUM};
+ public static final String VCS_DATE = "${VCS_DATE}";
+ public static final String VCS_BRANCH = "${VCS_BRANCH}";
+ public static final String VCS_TAG = "${VCS_TAG}";
+ public static final long VCS_TICK = ${VCS_TICK};
+ public static final String VCS_EXTRA = "${VCS_EXTRA}";
+
+ public static final String VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}";
+ public static final String VCS_FULL_HASH = "${VCS_FULL_HASH}";
+ public static final String VCS_SHORT_HASH = "${VCS_SHORT_HASH}";
+
+ public static final boolean VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+}
+EOF
+}
+
+# For Java properties output
+javapropOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 1) VCS_WC_MODIFIED="true" ;;
+ 0) VCS_WC_MODIFIED="false" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+# ${GENERATED_HEADER}
+
+VCS_TYPE=${VCS_TYPE}
+VCS_BASENAME=${VCS_BASENAME}
+VCS_UUID=${VCS_UUID}
+VCS_NUM=${VCS_NUM}
+VCS_DATE=${VCS_DATE}
+VCS_BRANCH=${VCS_BRANCH}
+VCS_TAG=${VCS_TAG}
+VCS_TICK=${VCS_TICK}
+VCS_EXTRA=${VCS_EXTRA}
+
+VCS_ACTION_STAMP=${VCS_ACTION_STAMP}
+VCS_FULL_HASH=${VCS_FULL_HASH}
+VCS_SHORT_HASH=${VCS_SHORT_HASH}
+
+VCS_WC_MODIFIED=${VCS_WC_MODIFIED}
+EOF
+}
+
+# For m4 output
+m4Output() {
+ cat >"${TARGETFILE}" <<EOF
+dnl ${GENERATED_HEADER}
+define(\`VCS_TYPE', \`${VCS_TYPE}')dnl
+define(\`VCS_BASENAME', \`${VCS_BASENAME}')dnl
+define(\`VCS_UUID', \`${VCS_UUID}')dnl
+define(\`VCS_NUM', \`${VCS_NUM}')dnl
+define(\`VCS_DATE', \`${VCS_DATE}')dnl
+define(\`VCS_BRANCH', \`${VCS_BRANCH}')dnl
+define(\`VCS_TAG', \`${VCS_TAG}')dnl
+define(\`VCS_TICK', \`${VCS_TICK}')dnl
+define(\`VCS_EXTRA', \`${VCS_EXTRA}')dnl
+define(\`VCS_ACTIONSTAMP', \`${VCS_ACTION_STAMP}')dnl
+define(\`VCS_FULLHASH', \`${VCS_FULL_HASH}')dnl
+define(\`VCS_SHORTHASH', \`${VCS_SHORT_HASH}')dnl
+define(\`VCS_WC_MODIFIED', \`${VCS_WC_MODIFIED}')dnl
+EOF
+}
+
+# For (La)TeX output
+texOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+% ${GENERATED_HEADER}
+\def \vcsType {${VCS_TYPE}}
+\def \vcsBasename {${VCS_BASENAME}}
+\def \vcsUUID {${VCS_UUID}}
+\def \vcsNum {${VCS_NUM}}
+\def \vcsDate {${VCS_DATE}}
+\def \vcsBranch {${VCS_BRANCH}}
+\def \vcsTag {${VCS_TAG}}
+\def \vcsTick {${VCS_TICK}}
+\def \vcsExtra {${VCS_EXTRA}}
+\def \vcsACTIONSTAMP {${VCS_ACTION_STAMP}}
+\def \vcsFullHash {${VCS_FULL_HASH}}
+\def \vcsShortHash {${VCS_SHORT_HASH}}
+\def \vcsWCModified {${VCS_WC_MODIFIED}}
+\endinput
+EOF
+}
+
+# For scheme output
+schemeOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="#f" ;;
+ 1) VCS_WC_MODIFIED="#t" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+;; ${GENERATED_HEADER}
+(define VCS_TYPE "${VCS_TYPE}")
+(define VCS_BASENAME "${VCS_BASENAME}")
+(define VCS_UUID "${VCS_UUID}")
+(define VCS_NUM ${VCS_NUM})
+(define VCS_DATE "${VCS_DATE}")
+(define VCS_BRANCH "${VCS_BRANCH}")
+(define VCS_TAG "${VCS_TAG}")
+(define VCS_TICK ${VCS_TICK})
+(define VCS_EXTRA "${VCS_EXTRA}")
+
+(define VCS_ACTION_STAMP "${VCS_ACTION_STAMP}")
+(define VCS_FULL_HASH "${VCS_FULL_HASH}")
+(define VCS_SHORT_HASH "${VCS_SHORT_HASH}")
+
+(define VCS_WC_MODIFIED ${VCS_WC_MODIFIED})
+;; end
+EOF
+}
+
+# For clojure output
+clojureOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="false" ;;
+ 1) VCS_WC_MODIFIED="true" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+;; ${GENERATED_HEADER}
+(def VCS_TYPE "${VCS_TYPE}")
+(def VCS_BASENAME "${VCS_BASENAME}")
+(def VCS_UUID "${VCS_UUID}")
+(def VCS_NUM ${VCS_NUM})
+(def VCS_DATE "${VCS_DATE}")
+(def VCS_BRANCH "${VCS_BRANCH}")
+(def VCS_TAG "${VCS_TAG}")
+(def VCS_TICK ${VCS_TICK})
+(def VCS_EXTRA "${VCS_EXTRA}")
+
+(def VCS_ACTION_STAMP "${VCS_ACTION_STAMP}")
+(def VCS_FULL_HASH "${VCS_FULL_HASH}")
+(def VCS_SHORT_HASH "${VCS_SHORT_HASH}")
+
+(def VCS_WC_MODIFIED ${VCS_WC_MODIFIED})
+;; end
+EOF
+}
+
+# For rpm spec file output
+rpmOutput() {
+ cat >"${TARGETFILE}" <<EOF
+# ${GENERATED_HEADER}
+$([ "${VCS_TYPE}" ] && echo "%define vcs_type ${VCS_TYPE}")
+$([ "${VCS_BASENAME}" ] && echo "%define vcs_basename ${VCS_BASENAME}")
+$([ "${VCS_UUID}" ] && echo "%define vcs_uuid ${VCS_UUID}")
+$([ "${VCS_NUM}" ] && echo "%define vcs_num ${VCS_NUM}")
+$([ "${VCS_DATE}" ] && echo "%define vcs_date ${VCS_DATE}")
+$([ "${VCS_BRANCH}" ] && echo "%define vcs_branch ${VCS_BRANCH}")
+$([ "${VCS_TAG}" ] && echo "%define vcs_tag ${VCS_TAG}")
+$([ "${VCS_TICK}" ] && echo "%define vcs_tick ${VCS_TICK}")
+$([ "${VCS_EXTRA}" ] && echo "%define vcs_extra ${VCS_EXTRA}")
+
+$([ "${VCS_ACTION_STAMP}" ] && echo "%define vcs_action_stamp ${VCS_ACTION_STAMP}")
+$([ "${VCS_FULL_HASH}" ] && echo "%define vcs_full_hash ${VCS_FULL_HASH}")
+$([ "${VCS_SHORT_HASH}" ] && echo "%define vcs_short_hash ${VCS_SHORT_HASH}")
+
+$([ "${VCS_WC_MODIFIED}" ] && echo "%define vcs_wc_modified ${VCS_WC_MODIFIED}")
+# end
+EOF
+}
+
+# shellcheck disable=SC2155,SC2039
+hppOutput() {
+ local NAMESPACE="$(echo "${VCS_BASENAME}" | sed -e 's:_::g' | tr '[:lower:]' '[:upper:]')"
+ cat >"${TARGETFILE}" <<EOF
+/* ${GENERATED_HEADER} */
+
+#ifndef ${NAMESPACE}_AUTOREVISION_H
+#define ${NAMESPACE}_AUTOREVISION_H
+
+#include <string>
+
+namespace $(echo "${NAMESPACE}" | tr '[:upper:]' '[:lower:]')
+{
+ const std::string VCS_TYPE = "${VCS_TYPE}";
+ const std::string VCS_BASENAME = "${VCS_BASENAME}";
+ const std::string VCS_UUID = "${VCS_UUID}";
+ const int VCS_NUM = ${VCS_NUM};
+ const std::string VCS_DATE = "${VCS_DATE}";
+ const std::string VCS_BRANCH = "${VCS_BRANCH}";
+ const std::string VCS_TAG = "${VCS_TAG}";
+ const int VCS_TICK = ${VCS_TICK};
+ const std::string VCS_EXTRA = "${VCS_EXTRA}";
+
+ const std::string VCS_ACTION_STAMP = "${VCS_ACTION_STAMP}";
+ const std::string VCS_FULL_HASH = "${VCS_FULL_HASH}";
+ const std::string VCS_SHORT_HASH = "${VCS_SHORT_HASH}";
+
+ const int VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+}
+
+#endif
+
+/* end */
+EOF
+}
+
+matlabOutput() {
+ case "${VCS_WC_MODIFIED}" in
+ 0) VCS_WC_MODIFIED="FALSE" ;;
+ 1) VCS_WC_MODIFIED="TRUE" ;;
+ esac
+ cat >"${TARGETFILE}" <<EOF
+% ${GENERATED_HEADER}
+
+VCS_TYPE = '${VCS_TYPE}';
+VCS_BASENAME = '${VCS_BASENAME}';
+VCS_UUID = '${VCS_UUID}';
+VCS_NUM = ${VCS_NUM};
+VCS_DATE = '${VCS_DATE}';
+VCS_BRANCH = '${VCS_BRANCH}';
+VCS_TAG = '${VCS_TAG}';
+VCS_TICK = ${VCS_TICK};
+VCS_EXTRA = '${VCS_EXTRA}';
+
+VCS_ACTION_STAMP = '${VCS_ACTION_STAMP}';
+VCS_FULL_HASH = '${VCS_FULL_HASH}';
+VCS_SHORT_HASH = '${VCS_SHORT_HASH}';
+
+VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+
+% end
+EOF
+}
+
+octaveOutput() {
+ cat >"${TARGETFILE}" <<EOF
+% ${GENERATED_HEADER}
+
+VCS_TYPE = '${VCS_TYPE}';
+VCS_BASENAME = '${VCS_BASENAME}';
+VCS_UUID = '${VCS_UUID}';
+VCS_NUM = ${VCS_NUM};
+VCS_DATE = '${VCS_DATE}';
+VCS_BRANCH = '${VCS_BRANCH}';
+VCS_TAG = '${VCS_TAG}';
+VCS_TICK = ${VCS_TICK};
+VCS_EXTRA = '${VCS_EXTRA}';
+
+VCS_ACTION_STAMP = '${VCS_ACTION_STAMP}';
+VCS_FULL_HASH = '${VCS_FULL_HASH}';
+VCS_SHORT_HASH = '${VCS_SHORT_HASH}';
+
+VCS_WC_MODIFIED = ${VCS_WC_MODIFIED};
+
+% end
+EOF
+}
+
+cmakeOutput() {
+ cat >"${TARGETFILE}" <<EOF
+# ${GENERATED_HEADER}
+
+set(VCS_TYPE ${VCS_TYPE})
+set(VCS_BASENAME ${VCS_BASENAME})
+set(VCS_UUID ${VCS_UUID})
+set(VCS_NUM ${VCS_NUM})
+set(VCS_DATE ${VCS_DATE})
+set(VCS_BRANCH ${VCS_BRANCH})
+set(VCS_TAG ${VCS_TAG})
+set(VCS_TICK ${VCS_TICK})
+set(VCS_EXTRA ${VCS_EXTRA})
+
+set(VCS_ACTION_STAMP ${VCS_ACTION_STAMP})
+set(VCS_FULL_HASH ${VCS_FULL_HASH})
+set(VCS_SHORT_HASH ${VCS_SHORT_HASH})
+
+set(VCS_WC_MODIFIED ${VCS_WC_MODIFIED})
+
+# end
+EOF
+}
+
+# Helper functions
+# Count path segments
+# shellcheck disable=SC2039
+pathSegment() {
+ local pathz="${1}"
+ local depth="0"
+
+ if [ ! -z "${pathz}" ]; then
+ # Continue until we are at / or there are no path separators left.
+ while [ ! "${pathz}" = "/" ] && [ ! "${pathz}" = "$(echo "${pathz}" | sed -e 's:/::')" ]; do
+ pathz="$(dirname "${pathz}")"
+ depth="$((depth + 1))"
+ done
+ fi
+ echo "${depth}"
+}
+
+# Largest of four numbers
+# shellcheck disable=SC2039
+multiCompare() {
+ local larger="${1}"
+ local numA="${2}"
+ local numB="${3}"
+ local numC="${4}"
+
+ [ "${numA}" -gt "${larger}" ] && larger="${numA}"
+ [ "${numB}" -gt "${larger}" ] && larger="${numB}"
+ [ "${numC}" -gt "${larger}" ] && larger="${numC}"
+ echo "${larger}"
+}
+
+# Test for repositories
+# shellcheck disable=SC2155,SC2039
+repoTest() {
+ REPONUM="0"
+ if [ ! -z "$(git rev-parse HEAD 2>/dev/null)" ]; then
+ local gitPath="$(git rev-parse --show-toplevel)"
+ local gitDepth="$(pathSegment "${gitPath}")"
+ REPONUM="$((REPONUM + 1))"
+ else
+ local gitDepth="0"
+ fi
+ if [ ! -z "$(hg root 2>/dev/null)" ]; then
+ local hgPath="$(hg root 2>/dev/null)"
+ local hgDepth="$(pathSegment "${hgPath}")"
+ REPONUM="$((REPONUM + 1))"
+ else
+ local hgDepth="0"
+ fi
+ if [ ! -z "$(bzr root 2>/dev/null)" ]; then
+ local bzrPath="$(bzr root 2>/dev/null)"
+ local bzrDepth="$(pathSegment "${bzrPath}")"
+ REPONUM="$((REPONUM + 1))"
+ else
+ local bzrDepth="0"
+ fi
+ if [ ! -z "$(svn info 2>/dev/null)" ]; then
+ local stringz="<wcroot-abspath>"
+ local stringx="</wcroot-abspath>"
+ local svnPath="$(svn info --xml | sed -n -e "s:${stringz}::" -e "s:${stringx}::p")"
+ # An old enough svn will not be able give us a path; default
+ # to 1 for that case.
+ if [ -z "${svnPath}" ]; then
+ local svnDepth="1"
+ else
+ local svnDepth="$(pathSegment "${svnPath}")"
+ fi
+ REPONUM="$((REPONUM + 1))"
+ else
+ local svnDepth="0"
+ fi
+
+ # Do not do more work then we have to.
+ if [ "${REPONUM}" = "0" ]; then
+ return
+ fi
+
+ # Figure out which repo is the deepest and use it.
+ local wonRepo="$(multiCompare "${gitDepth}" "${hgDepth}" "${bzrDepth}" "${svnDepth}")"
+ if [ "${wonRepo}" = "${gitDepth}" ]; then
+ gitRepo
+ elif [ "${wonRepo}" = "${hgDepth}" ]; then
+ hgRepo
+ elif [ "${wonRepo}" = "${bzrDepth}" ]; then
+ bzrRepo
+ elif [ "${wonRepo}" = "${svnDepth}" ]; then
+ svnRepo
+ fi
+}
+
+# Detect which repos we are in and gather data.
+# shellcheck source=/dev/null
+if [ -f "${CACHEFILE}" ] && [ "${CACHEFORCE}" = "1" ]; then
+ # When requested only read from the cache to populate our symbols.
+ . "${CACHEFILE}"
+else
+ # If a value is not set through the environment set VCS_EXTRA to nothing.
+ : "${VCS_EXTRA:=""}"
+ repoTest
+
+ if [ -f "${CACHEFILE}" ] && [ "${REPONUM}" = "0" ]; then
+ # We are not in a repo; try to use a previously generated cache to populate our symbols.
+ . "${CACHEFILE}"
+ # Do not overwrite the cache if we know we are not going to write anything new.
+ CACHEFORCE="1"
+ elif [ "${REPONUM}" = "0" ]; then
+ echo "error: No repo or cache detected." 1>&2
+ exit 1
+ fi
+fi
+
+# -s output is handled here.
+if [ ! -z "${VAROUT}" ]; then
+ if [ "${VAROUT}" = "VCS_TYPE" ]; then
+ echo "${VCS_TYPE}"
+ elif [ "${VAROUT}" = "VCS_BASENAME" ]; then
+ echo "${VCS_BASENAME}"
+ elif [ "${VAROUT}" = "VCS_NUM" ]; then
+ echo "${VCS_NUM}"
+ elif [ "${VAROUT}" = "VCS_DATE" ]; then
+ echo "${VCS_DATE}"
+ elif [ "${VAROUT}" = "VCS_BRANCH" ]; then
+ echo "${VCS_BRANCH}"
+ elif [ "${VAROUT}" = "VCS_TAG" ]; then
+ echo "${VCS_TAG}"
+ elif [ "${VAROUT}" = "VCS_TICK" ]; then
+ echo "${VCS_TICK}"
+ elif [ "${VAROUT}" = "VCS_FULL_HASH" ]; then
+ echo "${VCS_FULL_HASH}"
+ elif [ "${VAROUT}" = "VCS_SHORT_HASH" ]; then
+ echo "${VCS_SHORT_HASH}"
+ elif [ "${VAROUT}" = "VCS_WC_MODIFIED" ]; then
+ echo "${VCS_WC_MODIFIED}"
+ elif [ "${VAROUT}" = "VCS_ACTION_STAMP" ]; then
+ echo "${VCS_ACTION_STAMP}"
+ else
+ echo "error: Not a valid output symbol." 1>&2
+ exit 1
+ fi
+fi
+
+# Detect requested output type and use it.
+if [ ! -z "${AFILETYPE}" ]; then
+ if [ "${AFILETYPE}" = "c" ]; then
+ cOutput
+ elif [ "${AFILETYPE}" = "h" ]; then
+ hOutput
+ elif [ "${AFILETYPE}" = "xcode" ]; then
+ xcodeOutput
+ elif [ "${AFILETYPE}" = "swift" ]; then
+ swiftOutput
+ elif [ "${AFILETYPE}" = "sh" ]; then
+ shOutput
+ elif [ "${AFILETYPE}" = "py" ] || [ "${AFILETYPE}" = "python" ]; then
+ pyOutput
+ elif [ "${AFILETYPE}" = "pl" ] || [ "${AFILETYPE}" = "perl" ]; then
+ plOutput
+ elif [ "${AFILETYPE}" = "lua" ]; then
+ luaOutput
+ elif [ "${AFILETYPE}" = "php" ]; then
+ phpOutput
+ elif [ "${AFILETYPE}" = "ini" ]; then
+ iniOutput
+ elif [ "${AFILETYPE}" = "js" ]; then
+ jsOutput
+ elif [ "${AFILETYPE}" = "json" ]; then
+ jsonOutput
+ elif [ "${AFILETYPE}" = "java" ]; then
+ javaOutput
+ elif [ "${AFILETYPE}" = "javaprop" ]; then
+ javapropOutput
+ elif [ "${AFILETYPE}" = "tex" ]; then
+ texOutput
+ elif [ "${AFILETYPE}" = "m4" ]; then
+ m4Output
+ elif [ "${AFILETYPE}" = "scheme" ]; then
+ schemeOutput
+ elif [ "${AFILETYPE}" = "clojure" ]; then
+ clojureOutput
+ elif [ "${AFILETYPE}" = "rpm" ]; then
+ rpmOutput
+ elif [ "${AFILETYPE}" = "hpp" ]; then
+ hppOutput
+ elif [ "${AFILETYPE}" = "matlab" ]; then
+ matlabOutput
+ elif [ "${AFILETYPE}" = "octave" ]; then
+ octaveOutput
+ elif [ "${AFILETYPE}" = "cmake" ]; then
+ cmakeOutput
+ else
+ echo "error: Not a valid output type." 1>&2
+ exit 1
+ fi
+fi
+
+# If requested, make a cache file.
+if [ ! -z "${CACHEFILE}" ] && [ ! "${CACHEFORCE}" = "1" ]; then
+ TARGETFILE="${CACHEFILE}.tmp"
+ shOutput
+
+ # Check to see if there have been any actual changes.
+ if [ ! -f "${CACHEFILE}" ]; then
+ mv -f "${CACHEFILE}.tmp" "${CACHEFILE}"
+ elif cmp -s "${CACHEFILE}.tmp" "${CACHEFILE}"; then
+ rm -f "${CACHEFILE}.tmp"
+ else
+ mv -f "${CACHEFILE}.tmp" "${CACHEFILE}"
+ fi
+fi
diff --git a/ci/.gitkeep b/ci/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ci/.gitkeep
diff --git a/ci/get-nprocessors.sh b/ci/get-nprocessors.sh
new file mode 100644
index 0000000..8a754cf
--- /dev/null
+++ b/ci/get-nprocessors.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+# Copyright 2017 Google Inc.
+# All Rights Reserved.
+#
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# This file is typically sourced by another script.
+# if possible, ask for the precise number of processors,
+# otherwise take 2 processors as reasonable default; see
+# https://docs.travis-ci.com/user/speeding-up-the-build/#Makefile-optimization
+if [ -x /usr/bin/getconf ]; then
+ NPROCESSORS=$(/usr/bin/getconf _NPROCESSORS_ONLN)
+else
+ NPROCESSORS=2
+fi
+
+# as of 2017-09-04 Travis CI reports 32 processors, but GCC build
+# crashes if parallelized too much (maybe memory consumption problem),
+# so limit to 4 processors for the time being.
+if [ $NPROCESSORS -gt 4 ]; then
+ echo "$0:Note: Limiting processors to use by make from $NPROCESSORS to 4."
+ NPROCESSORS=4
+fi
diff --git a/ci/travis.sh b/ci/travis.sh
new file mode 100644
index 0000000..a5d12f6
--- /dev/null
+++ b/ci/travis.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env sh
+set -evx
+
+chmod +x ci/get-nprocessors.sh
+. ci/get-nprocessors.sh
+
+# if possible, ask for the precise number of processors,
+# otherwise take 2 processors as reasonable default; see
+# https://docs.travis-ci.com/user/speeding-up-the-build/#Makefile-optimization
+if [ -x /usr/bin/getconf ]; then
+ NPROCESSORS=$(/usr/bin/getconf _NPROCESSORS_ONLN)
+else
+ NPROCESSORS=2
+fi
+
+# as of 2017-09-04 Travis CI reports 32 processors, but GCC build
+# crashes if parallelized too much (maybe memory consumption problem),
+# so limit to 4 processors for the time being.
+if [ $NPROCESSORS -gt 4 ]; then
+ echo "$0:Note: Limiting processors to use by make from $NPROCESSORS to 4."
+ NPROCESSORS=4
+fi
+
+# Tell make to use the processors. No preceding '-' required.
+MAKEFLAGS="j${NPROCESSORS}"
+export MAKEFLAGS
+
+env | sort
+
+# Set default values to OFF for these variables if not specified.
+: "${NO_EXCEPTION:=OFF}"
+: "${NO_RTTI:=OFF}"
+: "${COMPILER_IS_GNUCXX:=OFF}"
+
+mkdir build || true
+cd build
+
+cmake3 -DCMAKE_CXX_FLAGS=$CXX_FLAGS \
+ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
+ -DASAN_OPTION=$ASAN_OPTION \
+ -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \
+ -DTFE_VERSION_DAILY_BUILD=$TESTING_VERSION_BUILD \
+ ..
+
+make
+
+if [ -n "${PACKAGE}" ]; then
+ make package
+fi \ No newline at end of file
diff --git a/cmake/.gitkeep b/cmake/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cmake/.gitkeep
diff --git a/cmake/Package.cmake b/cmake/Package.cmake
new file mode 100644
index 0000000..0ad6569
--- /dev/null
+++ b/cmake/Package.cmake
@@ -0,0 +1,43 @@
+cmake_minimum_required(VERSION 3.12)
+
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(CPACK_PACKAGE_NAME ${PROJECT_NAME}-debug)
+else()
+ set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
+endif()
+
+message(STATUS "Package: ${CPACK_PACKAGE_NAME}")
+
+set(CPACK_PACKAGE_VENDOR "TSG")
+set(CPACK_PACKAGE_VERSION_MAJOR "${HTTP_DECODER_VERSION_MAJOR}")
+set(CPACK_PACKAGE_VERSION_MINOR "${HTTP_DECODER_VERSION_MINOR}")
+set(CPACK_PACKAGE_VERSION_PATCH "${HTTP_DECODER_VERSION_PATCH}.${HTTP_DECODER_DESCRIBE}")
+set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
+
+
+# RPM Build
+set(CPACK_GENERATOR "RPM")
+set(CPACK_RPM_AUTO_GENERATED_FILE_NAME ON)
+set(CPACK_RPM_FILE_NAME "RPM-DEFAULT")
+set(CPACK_RPM_PACKAGE_AUTOREQPROV "no")
+set(CPACK_RPM_PACKAGE_RELEASE_DIST on)
+set(CPACK_RPM_DEBUGINFO_PACKAGE on)
+set(CPACK_BUILD_SOURCE_DIRS "${CMAKE_SOURCE_DIR}")
+
+set(CPACK_RPM_COMPONENT_INSTALL ON)
+set(CPACK_COMPONENTS_ALL Libraries Headers)
+
+set(CPACK_RPM_HEADERS_PACKAGE_NAME "${CPACK_PACKAGE_NAME}-devel")
+set(CPACK_RPM_LIBRARIES_PACKAGE_NAME "${CPACK_PACKAGE_NAME}")
+
+set(CPACK_RPM_PACKAGE_AUTOREQPROV "no")
+set(CPACK_RPM_PACKAGE_AUTOREQ "no")
+
+set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/cmake/PostInstall.in)
+set(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/cmake/PostUninstall.in)
+set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/cmake/PreInstall.in)
+set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE ${CMAKE_SOURCE_DIR}/cmake/PreUninstall.in)
+
+set(CPACK_RPM_HEADERS_PACKAGE_CONFLICTS ${CPACK_RPM_HEADERS_PACKAGE_NAME})
+set(CPACK_RPM_LIBRARIES_PACKAGE_CONFLICTS ${CPACK_RPM_LIBRARIES_PACKAGE_NAME})
+include(CPack)
diff --git a/cmake/PostInstall.in b/cmake/PostInstall.in
new file mode 100644
index 0000000..d601599
--- /dev/null
+++ b/cmake/PostInstall.in
@@ -0,0 +1 @@
+/sbin/ldconfig \ No newline at end of file
diff --git a/cmake/PostUninstall.in b/cmake/PostUninstall.in
new file mode 100644
index 0000000..d601599
--- /dev/null
+++ b/cmake/PostUninstall.in
@@ -0,0 +1 @@
+/sbin/ldconfig \ No newline at end of file
diff --git a/cmake/PreInstall.in b/cmake/PreInstall.in
new file mode 100644
index 0000000..ea89e39
--- /dev/null
+++ b/cmake/PreInstall.in
@@ -0,0 +1,22 @@
+DST=${RPM_INSTALL_PREFIX}/sapp/
+
+mkdir -p ${DST}/plug/stellar_on_sapp
+mkdir -p ${DST}/stellar_plugin/
+touch ${DST}/stellar_plugin/spec.toml
+touch ${DST}/plug/conflist.inf
+
+if ! grep -q '^\./plug/stellar_on_sapp/start_loader.inf$' "${DST}/plug/conflist.inf"; then
+ if grep -q '^\[platform\]$' "${DST}/plug/conflist.inf"; then
+ sed -i '/^\[platform\]$/a\./plug/stellar_on_sapp/start_loader.inf' "${DST}/plug/conflist.inf"
+ else
+ echo -e "[platform]\n./plug/stellar_on_sapp/start_loader.inf" >> "${DST}/plug/conflist.inf"
+ fi
+fi
+
+if ! grep -q '^\./plug/stellar_on_sapp/defer_loader.inf$' "${DST}/plug/conflist.inf"; then
+ if grep -q '^\[business\]$' "${DST}/plug/conflist.inf"; then
+ sed -i '/^\[business\]$/a\./plug/stellar_on_sapp/defer_loader.inf' "${DST}/plug/conflist.inf"
+ else
+ echo -e "[business]\n./plug/stellar_on_sapp/defer_loader.inf" >> "${DST}/plug/conflist.inf"
+ fi
+fi \ No newline at end of file
diff --git a/cmake/PreUninstall.in b/cmake/PreUninstall.in
new file mode 100644
index 0000000..dc0aa5e
--- /dev/null
+++ b/cmake/PreUninstall.in
@@ -0,0 +1,6 @@
+if [ $1 == 0 ]; then
+ DST=${RPM_INSTALL_PREFIX}/sapp
+ sed -i '/start_loader.inf/d' ${DST}/plug/conflist.inf
+ sed -i '/defer_loader.inf/d' ${DST}/plug/conflist.inf
+ rm ${DST}/stellar_plugin -rf
+fi
diff --git a/cmake/Version.cmake b/cmake/Version.cmake
new file mode 100644
index 0000000..2492869
--- /dev/null
+++ b/cmake/Version.cmake
@@ -0,0 +1,47 @@
+# Using autorevision.sh to generate version information
+set(__SOURCE_AUTORESIVISION ${CMAKE_SOURCE_DIR}/autorevision.sh)
+set(__AUTORESIVISION ${CMAKE_BINARY_DIR}/autorevision.sh)
+set(__VERSION_CACHE ${CMAKE_BINARY_DIR}/version.txt)
+set(__VERSION_CONFIG ${CMAKE_BINARY_DIR}/version.cmake)
+
+file(COPY ${__SOURCE_AUTORESIVISION} DESTINATION ${CMAKE_BINARY_DIR}
+ FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE
+ WORLD_READ WORLD_EXECUTE)
+
+# Execute autorevision.sh to generate version information
+execute_process(COMMAND ${__AUTORESIVISION} -t cmake -o ${__VERSION_CACHE} OUTPUT_FILE ${__VERSION_CONFIG})
+include(${__VERSION_CONFIG})
+
+# Extract major, minor, patch version from git tag
+string(REGEX REPLACE "^v([0-9]+)\\..*" "\\1" HTTP_DECODER_VERSION_MAJOR "${VCS_TAG}")
+string(REGEX REPLACE "^v[0-9]+\\.([0-9]+).*" "\\1" HTTP_DECODER_VERSION_MINOR "${VCS_TAG}")
+string(REGEX REPLACE "^v[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" HTTP_DECODER_VERSION_PATCH "${VCS_TAG}")
+string(REGEX REPLACE "[T\\:\\+\\-]" "" HTTP_DECODER_VERSION_DATE "${VCS_DATE}")
+
+if(STELLAR_VERSION_DAILY_BUILD)
+ set(HTTP_DECODER_VERSION_PATCH ${HTTP_DECODER_VERSION_PATCH}.${HTTP_DECODER_VERSION_DATE})
+endif()
+
+if(NOT HTTP_DECODER_VERSION_MAJOR)
+ set(HTTP_DECODER_VERSION_MAJOR 1)
+endif()
+
+if(NOT HTTP_DECODER_VERSION_MINOR)
+ set(HTTP_DECODER_VERSION_MINOR 0)
+endif()
+
+if(NOT HTTP_DECODER_VERSION_PATCH)
+ set(HTTP_DECODER_VERSION_PATCH 0)
+endif()
+
+set(HTTP_DECODER_DESCRIBE "${VCS_SHORT_HASH}")
+set(STELLAR_VERSION "${HTTP_DECODER_VERSION_MAJOR}.${HTTP_DECODER_VERSION_MINOR}.${HTTP_DECODER_VERSION_PATCH}")
+set(HTTP_DECODER_GIT_VERSION "${HTTP_DECODER_VERSION_MAJOR}.${HTTP_DECODER_VERSION_MINOR}.${HTTP_DECODER_VERSION_PATCH}-${HTTP_DECODER_DESCRIBE}")
+
+# Replace .- with _
+string(REGEX REPLACE "[\\.\\-]" "_" STELLAR_VAR_VERSION "${HTTP_DECODER_GIT_VERSION}")
+
+# Print information
+message(STATUS "Welcome to stateful network function development platform, Version: ${HTTP_DECODER_GIT_VERSION}")
+add_definitions(-DHTTP_DECODER_GIT_VERSION=\"${HTTP_DECODER_GIT_VERSION}\")
+add_definitions(-DHTTP_DECODER_VAR_VERSION=${HTTP_DECODER_VAR_VERSION}) \ No newline at end of file
diff --git a/conf/.gitkeep b/conf/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/conf/.gitkeep
diff --git a/conf/http_decoder.toml b/conf/http_decoder.toml
new file mode 100644
index 0000000..70ed3bd
--- /dev/null
+++ b/conf/http_decoder.toml
@@ -0,0 +1,15 @@
+[basic]
+# Switch for decompress body, open(1) closed(0), default(0)
+decompress=1
+
+# per session mempool size, default(32Ki)
+mempool_size=32768
+
+# per session http parsed result queue length
+result_queue_len=16
+
+# call fieldstat every stat_interval_pkts
+stat_interval_pkts=1000
+
+# fieldstat output interval
+stat_output_interval=1 \ No newline at end of file
diff --git a/deps/.gitkeep b/deps/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/deps/.gitkeep
diff --git a/deps/mempool/nmx_alloc.c b/deps/mempool/nmx_alloc.c
new file mode 100644
index 0000000..083f555
--- /dev/null
+++ b/deps/mempool/nmx_alloc.c
@@ -0,0 +1,44 @@
+#include "nmx_alloc.h"
+#include <string.h>
+
+void *nmx_alloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+
+ return p;
+}
+
+void *nmx_calloc(size_t size)
+{
+ void *p;
+
+ p = nmx_alloc(size);
+
+ if (p) {
+ memset(p,0,size);
+ }
+
+ return p;
+}
+
+void *nmx_realloc(void *p, size_t size){
+
+ if(p) {
+ return realloc (p, size);
+ }
+
+ return NULL;
+
+}
+
+void *nmx_memalign(size_t alignment, size_t size)
+{
+ void *p;
+ int err;
+
+ err = posix_memalign(&p, alignment, size);
+
+ return p;
+} \ No newline at end of file
diff --git a/deps/mempool/nmx_alloc.h b/deps/mempool/nmx_alloc.h
new file mode 100644
index 0000000..123d5bf
--- /dev/null
+++ b/deps/mempool/nmx_alloc.h
@@ -0,0 +1,16 @@
+#ifndef __nmx_alloc_H_
+#define __nmx_alloc_H_
+#include <stdlib.h>
+
+void *nmx_alloc(size_t size);
+void *nmx_calloc(size_t size);
+void *nmx_realloc(void *p, size_t size);
+
+#define nmx_free free
+
+#define nmx_align_ptr(p, a) \
+ (unsigned char *) (((unsigned long ) (p) + ((unsigned long ) a - 1)) & ~((unsigned long ) a - 1))
+
+void *nmx_memalign(size_t alignment, size_t size);
+
+#endif //nmx_alloc_H_
diff --git a/deps/mempool/nmx_palloc.c b/deps/mempool/nmx_palloc.c
new file mode 100644
index 0000000..307e2f6
--- /dev/null
+++ b/deps/mempool/nmx_palloc.c
@@ -0,0 +1,262 @@
+#include "nmx_palloc.h"
+
+#include <string.h>
+#include <unistd.h>
+
+size_t nmx_pagesize = 0;
+
+//#define nmx_MAX_ALLOC_FROM_POOL (nmx_pagesize - 1)
+
+#define NMX_MAX_ALLOC_FROM_POOL (nmx_pagesize == 0 ? (nmx_pagesize = getpagesize() -1) : (nmx_pagesize))
+
+#define NMX_POOL_ALIGNMENT 16
+
+static void *nmx_palloc_block(nmx_pool_t *pool, size_t size);
+static void *nmx_palloc_large(nmx_pool_t *pool, size_t size);
+
+nmx_pool_t *nmx_create_pool(size_t size)
+{
+ nmx_pool_t *p;
+
+ p = (nmx_pool_t *)nmx_memalign(NMX_POOL_ALIGNMENT, size);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ p->d.last = (unsigned char *) p + sizeof(nmx_pool_t);
+ p->d.end = (unsigned char *) p + size;
+ p->d.next = NULL;
+ p->d.failed = 0;
+
+ size = size - sizeof(nmx_pool_t);
+ p->max = (size < NMX_MAX_ALLOC_FROM_POOL) ? size : NMX_MAX_ALLOC_FROM_POOL;
+
+ p->current = p;
+ p->large = NULL;
+
+ return p;
+}
+
+void nmx_destroy_pool(nmx_pool_t *pool)
+{
+ nmx_pool_t *p, *n;
+ nmx_pool_large_t *l;
+
+ for (l = pool->large; l; l = l->next) {
+
+ if (l->alloc) {
+ nmx_free(l->alloc);
+ }
+ }
+
+ for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
+ nmx_free(p);
+
+ if (n == NULL) {
+ break;
+ }
+ }
+}
+
+void nmx_reset_pool(nmx_pool_t *pool)
+{
+ nmx_pool_t *p;
+ nmx_pool_large_t *l;
+
+ for (l = pool->large; l; l = l->next) {
+ if (l->alloc) {
+ nmx_free(l->alloc);
+ }
+ }
+
+ for (p = pool; p; p = p->d.next) {
+ p->d.last = (unsigned char *) p + sizeof(nmx_pool_t);
+ p->d.failed = 0;
+ }
+
+ pool->current = pool;
+ pool->large = NULL;
+}
+
+void *nmx_palloc(nmx_pool_t *pool, size_t size)
+{
+ unsigned char *m;
+ nmx_pool_t *p;
+
+ if (size <= pool->max) {
+
+ p = pool->current;
+
+ do {
+ m = nmx_align_ptr(p->d.last, NMX_POOL_ALIGNMENT);
+
+ if ((size_t) (p->d.end - m) >= size) {
+ p->d.last = m + size;
+
+ return m;
+ }
+
+ p = p->d.next;
+
+ } while (p);
+
+ return nmx_palloc_block(pool, size);
+ }
+
+ return nmx_palloc_large(pool, size);
+}
+
+
+void *nmx_pnalloc(nmx_pool_t *pool, size_t size)
+{
+ unsigned char *m;
+ nmx_pool_t *p;
+
+ if (size <= pool->max) {
+
+ p = pool->current;
+
+ do {
+ m = p->d.last;
+
+ if ((size_t) (p->d.end - m) >= size) {
+ p->d.last = m + size;
+
+ return m;
+ }
+
+ p = p->d.next;
+
+ } while (p);
+
+ return nmx_palloc_block(pool, size);
+ }
+
+ return nmx_palloc_large(pool, size);
+}
+
+
+static void *
+nmx_palloc_block(nmx_pool_t *pool, size_t size)
+{
+ unsigned char *m;
+ size_t psize;
+ nmx_pool_t *p, *new;
+
+ psize = (size_t) (pool->d.end - (unsigned char *) pool);
+
+ m = (unsigned char *)nmx_memalign(NMX_POOL_ALIGNMENT, psize);
+ if (m == NULL) {
+ return NULL;
+ }
+
+ new = (nmx_pool_t *) m;
+
+ new->d.end = m + psize;
+ new->d.next = NULL;
+ new->d.failed = 0;
+
+ m += sizeof(nmx_pool_data_t);
+ m = nmx_align_ptr(m, NMX_POOL_ALIGNMENT);
+ new->d.last = m + size;
+
+ for (p = pool->current; p->d.next; p = p->d.next) {
+ if (p->d.failed++ > 4) {
+ pool->current = p->d.next;
+ }
+ }
+
+ p->d.next = new;
+
+ return m;
+}
+
+static void *
+nmx_palloc_large(nmx_pool_t *pool,size_t size)
+{
+ void *p;
+ int n;
+ nmx_pool_large_t *large;
+
+ p = nmx_alloc(size);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ n = 0;
+
+ for (large = pool->large; large; large = large->next) {
+ if (large->alloc == NULL) {
+ large->alloc = p;
+ return p;
+ }
+
+ if (n++ > 3) {
+ break;
+ }
+ }
+
+ large = nmx_palloc(pool, sizeof(nmx_pool_large_t));
+ if (large == NULL) {
+ nmx_free(p);
+ return NULL;
+ }
+
+ large->alloc = p;
+ large->next = pool->large;
+ pool->large = large;
+
+ return p;
+}
+
+void *nmx_pmemalign(nmx_pool_t *pool, size_t size, size_t alignment)
+{
+ void *p;
+ nmx_pool_large_t *large;
+
+ p = nmx_memalign(alignment, size);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ large = nmx_palloc(pool, sizeof(nmx_pool_large_t));
+ if (large == NULL) {
+ nmx_free(p);
+ return NULL;
+ }
+
+ large->alloc = p;
+ large->next = pool->large;
+ pool->large = large;
+
+ return p;
+}
+
+int nmx_pfree(nmx_pool_t *pool, void *p)
+{
+ nmx_pool_large_t *l;
+
+ for (l = pool->large; l; l = l->next) {
+ if (p == l->alloc) {
+
+ nmx_free(l->alloc);
+ l->alloc = NULL;
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void *nmx_pcalloc(nmx_pool_t *pool, size_t size)
+{
+ void *p;
+
+ p = nmx_palloc(pool, size);
+ if (p) {
+ memset(p, 0,size);
+ }
+
+ return p;
+} \ No newline at end of file
diff --git a/deps/mempool/nmx_palloc.h b/deps/mempool/nmx_palloc.h
new file mode 100644
index 0000000..0b69be1
--- /dev/null
+++ b/deps/mempool/nmx_palloc.h
@@ -0,0 +1,58 @@
+#ifndef __nmx_palloc_H_
+#define __nmx_palloc_H_
+
+#include <stdlib.h>
+
+#include "nmx_alloc.h"
+
+typedef struct nmx_pool_large_s nmx_pool_large_t;
+typedef struct nmx_pool_s nmx_pool_t;
+
+struct nmx_pool_large_s {
+ nmx_pool_large_t *next;
+ void *alloc;
+};
+
+typedef struct {
+ unsigned char *last;
+ unsigned char *end;
+ nmx_pool_t *next;
+ unsigned int failed;
+} nmx_pool_data_t;
+
+struct nmx_pool_s {
+
+ nmx_pool_data_t d;
+ size_t max;
+ nmx_pool_t *current;
+ nmx_pool_large_t *large;
+};
+
+/* ======================================
+ * ++++++++ Library Open API ++++++++++
+ * ======================================
+ */
+
+void *nmx_alloc (size_t size);
+
+void *nmx_calloc (size_t size);
+
+nmx_pool_t *nmx_create_pool (size_t size);
+
+void nmx_destroy_pool (nmx_pool_t *pool);
+
+void nmx_reset_pool (nmx_pool_t *pool);
+
+void *nmx_palloc (nmx_pool_t *pool, size_t size);
+
+void *nmx_pnalloc (nmx_pool_t *pool, size_t size);
+
+void *nmx_prealloc (nmx_pool_t *pool, void *p, size_t size);
+
+void *nmx_pcalloc (nmx_pool_t *pool, size_t size);
+
+void *nmx_pmemalign (nmx_pool_t *pool, size_t size, size_t alignment);
+
+int nmx_pfree (nmx_pool_t *pool, void *p);
+
+#endif //nmx_palloc.h_H_
diff --git a/deps/toml/CMakeLists.txt b/deps/toml/CMakeLists.txt
new file mode 100644
index 0000000..504c0bf
--- /dev/null
+++ b/deps/toml/CMakeLists.txt
@@ -0,0 +1,3 @@
+set(CMAKE_C_FLAGS "-std=c99")
+add_definitions(-fPIC)
+add_library(toml STATIC toml.c) \ No newline at end of file
diff --git a/deps/toml/toml.c b/deps/toml/toml.c
new file mode 100644
index 0000000..fafe0da
--- /dev/null
+++ b/deps/toml/toml.c
@@ -0,0 +1,2379 @@
+/*
+
+ MIT License
+
+ Copyright (c) CK Tan
+ https://github.com/cktan/tomlc99
+
+ 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.
+
+*/
+#define _POSIX_C_SOURCE 200809L
+#include "toml.h"
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void *(*ppmalloc)(size_t) = malloc;
+static void (*ppfree)(void *) = free;
+
+void toml_set_memutil(void *(*xxmalloc)(size_t), void (*xxfree)(void *)) {
+ if (xxmalloc)
+ ppmalloc = xxmalloc;
+ if (xxfree)
+ ppfree = xxfree;
+}
+
+#define MALLOC(a) ppmalloc(a)
+#define FREE(a) ppfree(a)
+
+#define malloc(x) error - forbidden - use MALLOC instead
+#define free(x) error - forbidden - use FREE instead
+#define calloc(x, y) error - forbidden - use CALLOC instead
+
+static void *CALLOC(size_t nmemb, size_t sz) {
+ int nb = sz * nmemb;
+ void *p = MALLOC(nb);
+ if (p) {
+ memset(p, 0, nb);
+ }
+ return p;
+}
+
+// some old platforms define strdup macro -- drop it.
+#undef strdup
+#define strdup(x) error - forbidden - use STRDUP instead
+
+static char *STRDUP(const char *s) {
+ int len = strlen(s);
+ char *p = MALLOC(len + 1);
+ if (p) {
+ memcpy(p, s, len);
+ p[len] = 0;
+ }
+ return p;
+}
+
+// some old platforms define strndup macro -- drop it.
+#undef strndup
+#define strndup(x) error - forbiden - use STRNDUP instead
+
+static char *STRNDUP(const char *s, size_t n) {
+ size_t len = strnlen(s, n);
+ char *p = MALLOC(len + 1);
+ if (p) {
+ memcpy(p, s, len);
+ p[len] = 0;
+ }
+ return p;
+}
+
+/**
+ * Convert a char in utf8 into UCS, and store it in *ret.
+ * Return #bytes consumed or -1 on failure.
+ */
+int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret) {
+ const unsigned char *buf = (const unsigned char *)orig;
+ unsigned i = *buf++;
+ int64_t v;
+
+ /* 0x00000000 - 0x0000007F:
+ 0xxxxxxx
+ */
+ if (0 == (i >> 7)) {
+ if (len < 1)
+ return -1;
+ v = i;
+ return *ret = v, 1;
+ }
+ /* 0x00000080 - 0x000007FF:
+ 110xxxxx 10xxxxxx
+ */
+ if (0x6 == (i >> 5)) {
+ if (len < 2)
+ return -1;
+ v = i & 0x1f;
+ for (int j = 0; j < 1; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6))
+ return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char *)buf - orig;
+ }
+
+ /* 0x00000800 - 0x0000FFFF:
+ 1110xxxx 10xxxxxx 10xxxxxx
+ */
+ if (0xE == (i >> 4)) {
+ if (len < 3)
+ return -1;
+ v = i & 0x0F;
+ for (int j = 0; j < 2; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6))
+ return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char *)buf - orig;
+ }
+
+ /* 0x00010000 - 0x001FFFFF:
+ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (0x1E == (i >> 3)) {
+ if (len < 4)
+ return -1;
+ v = i & 0x07;
+ for (int j = 0; j < 3; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6))
+ return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char *)buf - orig;
+ }
+
+ /* 0x00200000 - 0x03FFFFFF:
+ 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (0x3E == (i >> 2)) {
+ if (len < 5)
+ return -1;
+ v = i & 0x03;
+ for (int j = 0; j < 4; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6))
+ return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char *)buf - orig;
+ }
+
+ /* 0x04000000 - 0x7FFFFFFF:
+ 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (0x7e == (i >> 1)) {
+ if (len < 6)
+ return -1;
+ v = i & 0x01;
+ for (int j = 0; j < 5; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6))
+ return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char *)buf - orig;
+ }
+ return -1;
+}
+
+/**
+ * Convert a UCS char to utf8 code, and return it in buf.
+ * Return #bytes used in buf to encode the char, or
+ * -1 on error.
+ */
+int toml_ucs_to_utf8(int64_t code, char buf[6]) {
+ /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16
+ */
+ /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well
+ * as 0xfffe and 0xffff (UCS noncharacters) should not appear in
+ * conforming UTF-8 streams.
+ */
+ if (0xd800 <= code && code <= 0xdfff)
+ return -1;
+ if (0xfffe <= code && code <= 0xffff)
+ return -1;
+
+ /* 0x00000000 - 0x0000007F:
+ 0xxxxxxx
+ */
+ if (code < 0)
+ return -1;
+ if (code <= 0x7F) {
+ buf[0] = (unsigned char)code;
+ return 1;
+ }
+
+ /* 0x00000080 - 0x000007FF:
+ 110xxxxx 10xxxxxx
+ */
+ if (code <= 0x000007FF) {
+ buf[0] = (unsigned char) (0xc0 | (code >> 6));
+ buf[1] = (unsigned char) (0x80 | (code & 0x3f));
+ return 2;
+ }
+
+ /* 0x00000800 - 0x0000FFFF:
+ 1110xxxx 10xxxxxx 10xxxxxx
+ */
+ if (code <= 0x0000FFFF) {
+ buf[0] = (unsigned char) (0xe0 | (code >> 12));
+ buf[1] = (unsigned char) (0x80 | ((code >> 6) & 0x3f));
+ buf[2] = (unsigned char) (0x80 | (code & 0x3f));
+ return 3;
+ }
+
+ /* 0x00010000 - 0x001FFFFF:
+ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (code <= 0x001FFFFF) {
+ buf[0] = (unsigned char) (0xf0 | (code >> 18));
+ buf[1] = (unsigned char) (0x80 | ((code >> 12) & 0x3f));
+ buf[2] = (unsigned char) (0x80 | ((code >> 6) & 0x3f));
+ buf[3] = (unsigned char) (0x80 | (code & 0x3f));
+ return 4;
+ }
+
+ /* 0x00200000 - 0x03FFFFFF:
+ 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (code <= 0x03FFFFFF) {
+ buf[0] = (unsigned char) (0xf8 | (code >> 24));
+ buf[1] = (unsigned char) (0x80 | ((code >> 18) & 0x3f));
+ buf[2] = (unsigned char) (0x80 | ((code >> 12) & 0x3f));
+ buf[3] = (unsigned char) (0x80 | ((code >> 6) & 0x3f));
+ buf[4] = (unsigned char) (0x80 | (code & 0x3f));
+ return 5;
+ }
+
+ /* 0x04000000 - 0x7FFFFFFF:
+ 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (code <= 0x7FFFFFFF) {
+ buf[0] = (unsigned char) (0xfc | (code >> 30));
+ buf[1] = (unsigned char) (0x80 | ((code >> 24) & 0x3f));
+ buf[2] = (unsigned char) (0x80 | ((code >> 18) & 0x3f));
+ buf[3] = (unsigned char) (0x80 | ((code >> 12) & 0x3f));
+ buf[4] = (unsigned char) (0x80 | ((code >> 6) & 0x3f));
+ buf[5] = (unsigned char) (0x80 | (code & 0x3f));
+ return 6;
+ }
+
+ return -1;
+}
+
+/*
+ * TOML has 3 data structures: value, array, table.
+ * Each of them can have identification key.
+ */
+typedef struct toml_keyval_t toml_keyval_t;
+struct toml_keyval_t {
+ const char *key; /* key to this value */
+ const char *val; /* the raw value */
+};
+
+typedef struct toml_arritem_t toml_arritem_t;
+struct toml_arritem_t {
+ int valtype; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime,
+ 'D'ate, 'T'imestamp */
+ char *val;
+ toml_array_t *arr;
+ toml_table_t *tab;
+};
+
+struct toml_array_t {
+ const char *key; /* key to this array */
+ int kind; /* element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed */
+ int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime,
+ 'D'ate, 'T'imestamp, 'm'ixed */
+
+ int nitem; /* number of elements */
+ toml_arritem_t *item;
+};
+
+struct toml_table_t {
+ const char *key; /* key to this table */
+ bool implicit; /* table was created implicitly */
+ bool readonly; /* no more modification allowed */
+
+ /* key-values in the table */
+ int nkval;
+ toml_keyval_t **kval;
+
+ /* arrays in the table */
+ int narr;
+ toml_array_t **arr;
+
+ /* tables in the table */
+ int ntab;
+ toml_table_t **tab;
+};
+
+static inline void xfree(const void *x) {
+ if (x)
+ FREE((void *)(intptr_t)x);
+}
+
+enum tokentype_t {
+ INVALID,
+ DOT,
+ COMMA,
+ EQUAL,
+ LBRACE,
+ RBRACE,
+ NEWLINE,
+ LBRACKET,
+ RBRACKET,
+ STRING,
+};
+typedef enum tokentype_t tokentype_t;
+
+typedef struct token_t token_t;
+struct token_t {
+ tokentype_t tok;
+ int lineno;
+ char *ptr; /* points into context->start */
+ int len;
+ int eof;
+};
+
+typedef struct context_t context_t;
+struct context_t {
+ char *start;
+ char *stop;
+ char *errbuf;
+ int errbufsz;
+
+ token_t tok;
+ toml_table_t *root;
+ toml_table_t *curtab;
+
+ struct {
+ int top;
+ char *key[10];
+ token_t tok[10];
+ } tpath;
+};
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+#define FLINE __FILE__ ":" TOSTRING(__LINE__)
+
+static int next_token(context_t *ctx, int dotisspecial);
+
+/*
+ Error reporting. Call when an error is detected. Always return -1.
+*/
+static int e_outofmemory(context_t *ctx, const char *fline) {
+ snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline);
+ return -1;
+}
+
+static int e_internal(context_t *ctx, const char *fline) {
+ snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline);
+ return -1;
+}
+
+static int e_syntax(context_t *ctx, int lineno, const char *msg) {
+ snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg);
+ return -1;
+}
+
+static int e_badkey(context_t *ctx, int lineno) {
+ snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno);
+ return -1;
+}
+
+static int e_keyexists(context_t *ctx, int lineno) {
+ snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno);
+ return -1;
+}
+
+static int e_forbid(context_t *ctx, int lineno, const char *msg) {
+ snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg);
+ return -1;
+}
+
+static void *expand(void *p, int sz, int newsz) {
+ void *s = MALLOC(newsz);
+ if (!s)
+ return 0;
+
+ memcpy(s, p, sz);
+ FREE(p);
+ return s;
+}
+
+static void **expand_ptrarr(void **p, int n) {
+ void **s = MALLOC((n + 1) * sizeof(void *));
+ if (!s)
+ return 0;
+
+ s[n] = 0;
+ memcpy(s, p, n * sizeof(void *));
+ FREE(p);
+ return s;
+}
+
+static toml_arritem_t *expand_arritem(toml_arritem_t *p, int n) {
+ toml_arritem_t *pp = expand(p, n * sizeof(*p), (n + 1) * sizeof(*p));
+ if (!pp)
+ return 0;
+
+ memset(&pp[n], 0, sizeof(pp[n]));
+ return pp;
+}
+
+static char *norm_lit_str(const char *src, int srclen, int multiline,
+ char *errbuf, int errbufsz) {
+ char *dst = 0; /* will write to dst[] and return it */
+ int max = 0; /* max size of dst[] */
+ int off = 0; /* cur offset in dst[] */
+ const char *sp = src;
+ const char *sq = src + srclen;
+ int ch;
+
+ /* scan forward on src */
+ for (;;) {
+ if (off >= max - 10) { /* have some slack for misc stuff */
+ int newmax = max + 50;
+ char *x = expand(dst, max, newmax);
+ if (!x) {
+ xfree(dst);
+ snprintf(errbuf, errbufsz, "out of memory");
+ return 0;
+ }
+ dst = x;
+ max = newmax;
+ }
+
+ /* finished? */
+ if (sp >= sq)
+ break;
+
+ ch = *sp++;
+ /* control characters other than tab is not allowed */
+ if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) || (ch == 0x7f)) {
+ if (!(multiline && (ch == '\r' || ch == '\n'))) {
+ xfree(dst);
+ snprintf(errbuf, errbufsz, "invalid char U+%04x", ch);
+ return 0;
+ }
+ }
+
+ // a plain copy suffice
+ dst[off++] = ch;
+ }
+
+ dst[off++] = 0;
+ return dst;
+}
+
+/*
+ * Convert src to raw unescaped utf-8 string.
+ * Returns NULL if error with errmsg in errbuf.
+ */
+static char *norm_basic_str(const char *src, int srclen, int multiline,
+ char *errbuf, int errbufsz) {
+ char *dst = 0; /* will write to dst[] and return it */
+ int max = 0; /* max size of dst[] */
+ int off = 0; /* cur offset in dst[] */
+ const char *sp = src;
+ const char *sq = src + srclen;
+ int ch;
+
+ /* scan forward on src */
+ for (;;) {
+ if (off >= max - 10) { /* have some slack for misc stuff */
+ int newmax = max + 50;
+ char *x = expand(dst, max, newmax);
+ if (!x) {
+ xfree(dst);
+ snprintf(errbuf, errbufsz, "out of memory");
+ return 0;
+ }
+ dst = x;
+ max = newmax;
+ }
+
+ /* finished? */
+ if (sp >= sq)
+ break;
+
+ ch = *sp++;
+ if (ch != '\\') {
+ /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F
+ */
+ if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) ||
+ (ch == 0x7f)) {
+ if (!(multiline && (ch == '\r' || ch == '\n'))) {
+ xfree(dst);
+ snprintf(errbuf, errbufsz, "invalid char U+%04x", ch);
+ return 0;
+ }
+ }
+
+ // a plain copy suffice
+ dst[off++] = ch;
+ continue;
+ }
+
+ /* ch was backslash. we expect the escape char. */
+ if (sp >= sq) {
+ snprintf(errbuf, errbufsz, "last backslash is invalid");
+ xfree(dst);
+ return 0;
+ }
+
+ /* for multi-line, we want to kill line-ending-backslash ... */
+ if (multiline) {
+
+ // if there is only whitespace after the backslash ...
+ if (sp[strspn(sp, " \t\r")] == '\n') {
+ /* skip all the following whitespaces */
+ sp += strspn(sp, " \t\r\n");
+ continue;
+ }
+ }
+
+ /* get the escaped char */
+ ch = *sp++;
+ switch (ch) {
+ case 'u':
+ case 'U': {
+ int64_t ucs = 0;
+ int nhex = (ch == 'u' ? 4 : 8);
+ for (int i = 0; i < nhex; i++) {
+ if (sp >= sq) {
+ snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex);
+ xfree(dst);
+ return 0;
+ }
+ ch = *sp++;
+ int v = ('0' <= ch && ch <= '9')
+ ? ch - '0'
+ : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1);
+ if (-1 == v) {
+ snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U");
+ xfree(dst);
+ return 0;
+ }
+ ucs = ucs * 16 + v;
+ }
+ int n = toml_ucs_to_utf8(ucs, &dst[off]);
+ if (-1 == n) {
+ snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U");
+ xfree(dst);
+ return 0;
+ }
+ off += n;
+ }
+ continue;
+
+ case 'b':
+ ch = '\b';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ case 'n':
+ ch = '\n';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case '"':
+ ch = '"';
+ break;
+ case '\\':
+ ch = '\\';
+ break;
+ default:
+ snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch);
+ xfree(dst);
+ return 0;
+ }
+
+ dst[off++] = ch;
+ }
+
+ // Cap with NUL and return it.
+ dst[off++] = 0;
+ return dst;
+}
+
+/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */
+static char *normalize_key(context_t *ctx, token_t strtok) {
+ const char *sp = strtok.ptr;
+ const char *sq = strtok.ptr + strtok.len;
+ int lineno = strtok.lineno;
+ char *ret;
+ int ch = *sp;
+ char ebuf[80];
+
+ /* handle quoted string */
+ if (ch == '\'' || ch == '\"') {
+ /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */
+ int multiline = 0;
+ if (sp[1] == ch && sp[2] == ch) {
+ sp += 3, sq -= 3;
+ multiline = 1;
+ } else
+ sp++, sq--;
+
+ if (ch == '\'') {
+ /* for single quote, take it verbatim. */
+ if (!(ret = STRNDUP(sp, sq - sp))) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ } else {
+ /* for double quote, we need to normalize */
+ ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf));
+ if (!ret) {
+ e_syntax(ctx, lineno, ebuf);
+ return 0;
+ }
+ }
+
+ /* newlines are not allowed in keys */
+ if (strchr(ret, '\n')) {
+ xfree(ret);
+ e_badkey(ctx, lineno);
+ return 0;
+ }
+ return ret;
+ }
+
+ /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */
+ const char *xp;
+ for (xp = sp; xp != sq; xp++) {
+ int k = *xp;
+ if (isalnum(k))
+ continue;
+ if (k == '_' || k == '-')
+ continue;
+ e_badkey(ctx, lineno);
+ return 0;
+ }
+
+ /* dup and return it */
+ if (!(ret = STRNDUP(sp, sq - sp))) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ return ret;
+}
+
+/*
+ * Look up key in tab. Return 0 if not found, or
+ * 'v'alue, 'a'rray or 't'able depending on the element.
+ */
+static int check_key(toml_table_t *tab, const char *key,
+ toml_keyval_t **ret_val, toml_array_t **ret_arr,
+ toml_table_t **ret_tab) {
+ int i;
+ void *dummy;
+
+ if (!ret_tab)
+ ret_tab = (toml_table_t **)&dummy;
+ if (!ret_arr)
+ ret_arr = (toml_array_t **)&dummy;
+ if (!ret_val)
+ ret_val = (toml_keyval_t **)&dummy;
+
+ *ret_tab = 0;
+ *ret_arr = 0;
+ *ret_val = 0;
+
+ for (i = 0; i < tab->nkval; i++) {
+ if (0 == strcmp(key, tab->kval[i]->key)) {
+ *ret_val = tab->kval[i];
+ return 'v';
+ }
+ }
+ for (i = 0; i < tab->narr; i++) {
+ if (0 == strcmp(key, tab->arr[i]->key)) {
+ *ret_arr = tab->arr[i];
+ return 'a';
+ }
+ }
+ for (i = 0; i < tab->ntab; i++) {
+ if (0 == strcmp(key, tab->tab[i]->key)) {
+ *ret_tab = tab->tab[i];
+ return 't';
+ }
+ }
+ return 0;
+}
+
+static int key_kind(toml_table_t *tab, const char *key) {
+ return check_key(tab, key, 0, 0, 0);
+}
+
+/* Create a keyval in the table.
+ */
+static toml_keyval_t *create_keyval_in_table(context_t *ctx, toml_table_t *tab,
+ token_t keytok) {
+ /* first, normalize the key to be used for lookup.
+ * remember to free it if we error out.
+ */
+ char *newkey = normalize_key(ctx, keytok);
+ if (!newkey)
+ return 0;
+
+ /* if key exists: error out. */
+ toml_keyval_t *dest = 0;
+ if (key_kind(tab, newkey)) {
+ xfree(newkey);
+ e_keyexists(ctx, keytok.lineno);
+ return 0;
+ }
+
+ /* make a new entry */
+ int n = tab->nkval;
+ toml_keyval_t **base;
+ if (0 == (base = (toml_keyval_t **)expand_ptrarr((void **)tab->kval, n))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ tab->kval = base;
+
+ if (0 == (base[n] = (toml_keyval_t *)CALLOC(1, sizeof(*base[n])))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ dest = tab->kval[tab->nkval++];
+
+ /* save the key in the new value struct */
+ dest->key = newkey;
+ return dest;
+}
+
+/* Create a table in the table.
+ */
+static toml_table_t *create_keytable_in_table(context_t *ctx, toml_table_t *tab,
+ token_t keytok) {
+ /* first, normalize the key to be used for lookup.
+ * remember to free it if we error out.
+ */
+ char *newkey = normalize_key(ctx, keytok);
+ if (!newkey)
+ return 0;
+
+ /* if key exists: error out */
+ toml_table_t *dest = 0;
+ if (check_key(tab, newkey, 0, 0, &dest)) {
+ xfree(newkey); /* don't need this anymore */
+
+ /* special case: if table exists, but was created implicitly ... */
+ if (dest && dest->implicit) {
+ /* we make it explicit now, and simply return it. */
+ dest->implicit = false;
+ return dest;
+ }
+ e_keyexists(ctx, keytok.lineno);
+ return 0;
+ }
+
+ /* create a new table entry */
+ int n = tab->ntab;
+ toml_table_t **base;
+ if (0 == (base = (toml_table_t **)expand_ptrarr((void **)tab->tab, n))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ tab->tab = base;
+
+ if (0 == (base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n])))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ dest = tab->tab[tab->ntab++];
+
+ /* save the key in the new table struct */
+ dest->key = newkey;
+ return dest;
+}
+
+/* Create an array in the table.
+ */
+static toml_array_t *create_keyarray_in_table(context_t *ctx, toml_table_t *tab,
+ token_t keytok, char kind) {
+ /* first, normalize the key to be used for lookup.
+ * remember to free it if we error out.
+ */
+ char *newkey = normalize_key(ctx, keytok);
+ if (!newkey)
+ return 0;
+
+ /* if key exists: error out */
+ if (key_kind(tab, newkey)) {
+ xfree(newkey); /* don't need this anymore */
+ e_keyexists(ctx, keytok.lineno);
+ return 0;
+ }
+
+ /* make a new array entry */
+ int n = tab->narr;
+ toml_array_t **base;
+ if (0 == (base = (toml_array_t **)expand_ptrarr((void **)tab->arr, n))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ tab->arr = base;
+
+ if (0 == (base[n] = (toml_array_t *)CALLOC(1, sizeof(*base[n])))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ toml_array_t *dest = tab->arr[tab->narr++];
+
+ /* save the key in the new array struct */
+ dest->key = newkey;
+ dest->kind = kind;
+ return dest;
+}
+
+static toml_arritem_t *create_value_in_array(context_t *ctx,
+ toml_array_t *parent) {
+ const int n = parent->nitem;
+ toml_arritem_t *base = expand_arritem(parent->item, n);
+ if (!base) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ parent->item = base;
+ parent->nitem++;
+ return &parent->item[n];
+}
+
+/* Create an array in an array
+ */
+static toml_array_t *create_array_in_array(context_t *ctx,
+ toml_array_t *parent) {
+ const int n = parent->nitem;
+ toml_arritem_t *base = expand_arritem(parent->item, n);
+ if (!base) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ toml_array_t *ret = (toml_array_t *)CALLOC(1, sizeof(toml_array_t));
+ if (!ret) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ base[n].arr = ret;
+ parent->item = base;
+ parent->nitem++;
+ return ret;
+}
+
+/* Create a table in an array
+ */
+static toml_table_t *create_table_in_array(context_t *ctx,
+ toml_array_t *parent) {
+ int n = parent->nitem;
+ toml_arritem_t *base = expand_arritem(parent->item, n);
+ if (!base) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ toml_table_t *ret = (toml_table_t *)CALLOC(1, sizeof(toml_table_t));
+ if (!ret) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ base[n].tab = ret;
+ parent->item = base;
+ parent->nitem++;
+ return ret;
+}
+
+static int skip_newlines(context_t *ctx, int isdotspecial) {
+ while (ctx->tok.tok == NEWLINE) {
+ if (next_token(ctx, isdotspecial))
+ return -1;
+ if (ctx->tok.eof)
+ break;
+ }
+ return 0;
+}
+
+static int parse_keyval(context_t *ctx, toml_table_t *tab);
+
+static inline int eat_token(context_t *ctx, tokentype_t typ, int isdotspecial,
+ const char *fline) {
+ if (ctx->tok.tok != typ)
+ return e_internal(ctx, fline);
+
+ if (next_token(ctx, isdotspecial))
+ return -1;
+
+ return 0;
+}
+
+/* We are at '{ ... }'.
+ * Parse the table.
+ */
+static int parse_inline_table(context_t *ctx, toml_table_t *tab) {
+ if (eat_token(ctx, LBRACE, 1, FLINE))
+ return -1;
+
+ for (;;) {
+ if (ctx->tok.tok == NEWLINE)
+ return e_syntax(ctx, ctx->tok.lineno,
+ "newline not allowed in inline table");
+
+ /* until } */
+ if (ctx->tok.tok == RBRACE)
+ break;
+
+ if (ctx->tok.tok != STRING)
+ return e_syntax(ctx, ctx->tok.lineno, "expect a string");
+
+ if (parse_keyval(ctx, tab))
+ return -1;
+
+ if (ctx->tok.tok == NEWLINE)
+ return e_syntax(ctx, ctx->tok.lineno,
+ "newline not allowed in inline table");
+
+ /* on comma, continue to scan for next keyval */
+ if (ctx->tok.tok == COMMA) {
+ if (eat_token(ctx, COMMA, 1, FLINE))
+ return -1;
+ continue;
+ }
+ break;
+ }
+
+ if (eat_token(ctx, RBRACE, 1, FLINE))
+ return -1;
+
+ tab->readonly = 1;
+
+ return 0;
+}
+
+static int valtype(const char *val) {
+ toml_timestamp_t ts;
+ if (*val == '\'' || *val == '"')
+ return 's';
+ if (0 == toml_rtob(val, 0))
+ return 'b';
+ if (0 == toml_rtoi(val, 0))
+ return 'i';
+ if (0 == toml_rtod(val, 0))
+ return 'd';
+ if (0 == toml_rtots(val, &ts)) {
+ if (ts.year && ts.hour)
+ return 'T'; /* timestamp */
+ if (ts.year)
+ return 'D'; /* date */
+ return 't'; /* time */
+ }
+ return 'u'; /* unknown */
+}
+
+/* We are at '[...]' */
+static int parse_array(context_t *ctx, toml_array_t *arr) {
+ if (eat_token(ctx, LBRACKET, 0, FLINE))
+ return -1;
+
+ for (;;) {
+ if (skip_newlines(ctx, 0))
+ return -1;
+
+ /* until ] */
+ if (ctx->tok.tok == RBRACKET)
+ break;
+
+ switch (ctx->tok.tok) {
+ case STRING: {
+ /* set array kind if this will be the first entry */
+ if (arr->kind == 0)
+ arr->kind = 'v';
+ else if (arr->kind != 'v')
+ arr->kind = 'm';
+
+ char *val = ctx->tok.ptr;
+ int vlen = ctx->tok.len;
+
+ /* make a new value in array */
+ toml_arritem_t *newval = create_value_in_array(ctx, arr);
+ if (!newval)
+ return e_outofmemory(ctx, FLINE);
+
+ if (!(newval->val = STRNDUP(val, vlen)))
+ return e_outofmemory(ctx, FLINE);
+
+ newval->valtype = valtype(newval->val);
+
+ /* set array type if this is the first entry */
+ if (arr->nitem == 1)
+ arr->type = newval->valtype;
+ else if (arr->type != newval->valtype)
+ arr->type = 'm'; /* mixed */
+
+ if (eat_token(ctx, STRING, 0, FLINE))
+ return -1;
+ break;
+ }
+
+ case LBRACKET: { /* [ [array], [array] ... ] */
+ /* set the array kind if this will be the first entry */
+ if (arr->kind == 0)
+ arr->kind = 'a';
+ else if (arr->kind != 'a')
+ arr->kind = 'm';
+
+ toml_array_t *subarr = create_array_in_array(ctx, arr);
+ if (!subarr)
+ return -1;
+ if (parse_array(ctx, subarr))
+ return -1;
+ break;
+ }
+
+ case LBRACE: { /* [ {table}, {table} ... ] */
+ /* set the array kind if this will be the first entry */
+ if (arr->kind == 0)
+ arr->kind = 't';
+ else if (arr->kind != 't')
+ arr->kind = 'm';
+
+ toml_table_t *subtab = create_table_in_array(ctx, arr);
+ if (!subtab)
+ return -1;
+ if (parse_inline_table(ctx, subtab))
+ return -1;
+ break;
+ }
+
+ default:
+ return e_syntax(ctx, ctx->tok.lineno, "syntax error");
+ }
+
+ if (skip_newlines(ctx, 0))
+ return -1;
+
+ /* on comma, continue to scan for next element */
+ if (ctx->tok.tok == COMMA) {
+ if (eat_token(ctx, COMMA, 0, FLINE))
+ return -1;
+ continue;
+ }
+ break;
+ }
+
+ if (eat_token(ctx, RBRACKET, 1, FLINE))
+ return -1;
+ return 0;
+}
+
+/* handle lines like these:
+ key = "value"
+ key = [ array ]
+ key = { table }
+*/
+static int parse_keyval(context_t *ctx, toml_table_t *tab) {
+ if (tab->readonly) {
+ return e_forbid(ctx, ctx->tok.lineno,
+ "cannot insert new entry into existing table");
+ }
+
+ token_t key = ctx->tok;
+ if (eat_token(ctx, STRING, 1, FLINE))
+ return -1;
+
+ if (ctx->tok.tok == DOT) {
+ /* handle inline dotted key.
+ e.g.
+ physical.color = "orange"
+ physical.shape = "round"
+ */
+ toml_table_t *subtab = 0;
+ {
+ char *subtabstr = normalize_key(ctx, key);
+ if (!subtabstr)
+ return -1;
+
+ subtab = toml_table_in(tab, subtabstr);
+ xfree(subtabstr);
+ }
+ if (!subtab) {
+ subtab = create_keytable_in_table(ctx, tab, key);
+ if (!subtab)
+ return -1;
+ }
+ if (next_token(ctx, 1))
+ return -1;
+ if (parse_keyval(ctx, subtab))
+ return -1;
+ return 0;
+ }
+
+ if (ctx->tok.tok != EQUAL) {
+ return e_syntax(ctx, ctx->tok.lineno, "missing =");
+ }
+
+ if (next_token(ctx, 0))
+ return -1;
+
+ switch (ctx->tok.tok) {
+ case STRING: { /* key = "value" */
+ toml_keyval_t *keyval = create_keyval_in_table(ctx, tab, key);
+ if (!keyval)
+ return -1;
+ token_t val = ctx->tok;
+
+ assert(keyval->val == 0);
+ if (!(keyval->val = STRNDUP(val.ptr, val.len)))
+ return e_outofmemory(ctx, FLINE);
+
+ if (next_token(ctx, 1))
+ return -1;
+
+ return 0;
+ }
+
+ case LBRACKET: { /* key = [ array ] */
+ toml_array_t *arr = create_keyarray_in_table(ctx, tab, key, 0);
+ if (!arr)
+ return -1;
+ if (parse_array(ctx, arr))
+ return -1;
+ return 0;
+ }
+
+ case LBRACE: { /* key = { table } */
+ toml_table_t *nxttab = create_keytable_in_table(ctx, tab, key);
+ if (!nxttab)
+ return -1;
+ if (parse_inline_table(ctx, nxttab))
+ return -1;
+ return 0;
+ }
+
+ default:
+ return e_syntax(ctx, ctx->tok.lineno, "syntax error");
+ }
+ return 0;
+}
+
+typedef struct tabpath_t tabpath_t;
+struct tabpath_t {
+ int cnt;
+ token_t key[10];
+};
+
+/* at [x.y.z] or [[x.y.z]]
+ * Scan forward and fill tabpath until it enters ] or ]]
+ * There will be at least one entry on return.
+ */
+static int fill_tabpath(context_t *ctx) {
+ int lineno = ctx->tok.lineno;
+ int i;
+
+ /* clear tpath */
+ for (i = 0; i < ctx->tpath.top; i++) {
+ char **p = &ctx->tpath.key[i];
+ xfree(*p);
+ *p = 0;
+ }
+ ctx->tpath.top = 0;
+
+ for (;;) {
+ if (ctx->tpath.top >= 10)
+ return e_syntax(ctx, lineno,
+ "table path is too deep; max allowed is 10.");
+
+ if (ctx->tok.tok != STRING)
+ return e_syntax(ctx, lineno, "invalid or missing key");
+
+ char *key = normalize_key(ctx, ctx->tok);
+ if (!key)
+ return -1;
+ ctx->tpath.tok[ctx->tpath.top] = ctx->tok;
+ ctx->tpath.key[ctx->tpath.top] = key;
+ ctx->tpath.top++;
+
+ if (next_token(ctx, 1))
+ return -1;
+
+ if (ctx->tok.tok == RBRACKET)
+ break;
+
+ if (ctx->tok.tok != DOT)
+ return e_syntax(ctx, lineno, "invalid key");
+
+ if (next_token(ctx, 1))
+ return -1;
+ }
+
+ if (ctx->tpath.top <= 0)
+ return e_syntax(ctx, lineno, "empty table selector");
+
+ return 0;
+}
+
+/* Walk tabpath from the root, and create new tables on the way.
+ * Sets ctx->curtab to the final table.
+ */
+static int walk_tabpath(context_t *ctx) {
+ /* start from root */
+ toml_table_t *curtab = ctx->root;
+
+ for (int i = 0; i < ctx->tpath.top; i++) {
+ const char *key = ctx->tpath.key[i];
+
+ toml_keyval_t *nextval = 0;
+ toml_array_t *nextarr = 0;
+ toml_table_t *nexttab = 0;
+ switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) {
+ case 't':
+ /* found a table. nexttab is where we will go next. */
+ break;
+
+ case 'a':
+ /* found an array. nexttab is the last table in the array. */
+ if (nextarr->kind != 't')
+ return e_internal(ctx, FLINE);
+
+ if (nextarr->nitem == 0)
+ return e_internal(ctx, FLINE);
+
+ nexttab = nextarr->item[nextarr->nitem - 1].tab;
+ break;
+
+ case 'v':
+ return e_keyexists(ctx, ctx->tpath.tok[i].lineno);
+
+ default: { /* Not found. Let's create an implicit table. */
+ int n = curtab->ntab;
+ toml_table_t **base =
+ (toml_table_t **)expand_ptrarr((void **)curtab->tab, n);
+ if (0 == base)
+ return e_outofmemory(ctx, FLINE);
+
+ curtab->tab = base;
+
+ if (0 == (base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n]))))
+ return e_outofmemory(ctx, FLINE);
+
+ if (0 == (base[n]->key = STRDUP(key)))
+ return e_outofmemory(ctx, FLINE);
+
+ nexttab = curtab->tab[curtab->ntab++];
+
+ /* tabs created by walk_tabpath are considered implicit */
+ nexttab->implicit = true;
+ } break;
+ }
+
+ /* switch to next tab */
+ curtab = nexttab;
+ }
+
+ /* save it */
+ ctx->curtab = curtab;
+
+ return 0;
+}
+
+/* handle lines like [x.y.z] or [[x.y.z]] */
+static int parse_select(context_t *ctx) {
+ assert(ctx->tok.tok == LBRACKET);
+
+ /* true if [[ */
+ int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '[');
+ /* need to detect '[[' on our own because next_token() will skip whitespace,
+ and '[ [' would be taken as '[[', which is wrong. */
+
+ /* eat [ or [[ */
+ if (eat_token(ctx, LBRACKET, 1, FLINE))
+ return -1;
+ if (llb) {
+ assert(ctx->tok.tok == LBRACKET);
+ if (eat_token(ctx, LBRACKET, 1, FLINE))
+ return -1;
+ }
+
+ if (fill_tabpath(ctx))
+ return -1;
+
+ /* For [x.y.z] or [[x.y.z]], remove z from tpath.
+ */
+ token_t z = ctx->tpath.tok[ctx->tpath.top - 1];
+ xfree(ctx->tpath.key[ctx->tpath.top - 1]);
+ ctx->tpath.top--;
+
+ /* set up ctx->curtab */
+ if (walk_tabpath(ctx))
+ return -1;
+
+ if (!llb) {
+ /* [x.y.z] -> create z = {} in x.y */
+ toml_table_t *curtab = create_keytable_in_table(ctx, ctx->curtab, z);
+ if (!curtab)
+ return -1;
+ ctx->curtab = curtab;
+ } else {
+ /* [[x.y.z]] -> create z = [] in x.y */
+ toml_array_t *arr = 0;
+ {
+ char *zstr = normalize_key(ctx, z);
+ if (!zstr)
+ return -1;
+ arr = toml_array_in(ctx->curtab, zstr);
+ xfree(zstr);
+ }
+ if (!arr) {
+ arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't');
+ if (!arr)
+ return -1;
+ }
+ if (arr->kind != 't')
+ return e_syntax(ctx, z.lineno, "array mismatch");
+
+ /* add to z[] */
+ toml_table_t *dest;
+ {
+ toml_table_t *t = create_table_in_array(ctx, arr);
+ if (!t)
+ return -1;
+
+ if (0 == (t->key = STRDUP("__anon__")))
+ return e_outofmemory(ctx, FLINE);
+
+ dest = t;
+ }
+
+ ctx->curtab = dest;
+ }
+
+ if (ctx->tok.tok != RBRACKET) {
+ return e_syntax(ctx, ctx->tok.lineno, "expects ]");
+ }
+ if (llb) {
+ if (!(ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) {
+ return e_syntax(ctx, ctx->tok.lineno, "expects ]]");
+ }
+ if (eat_token(ctx, RBRACKET, 1, FLINE))
+ return -1;
+ }
+
+ if (eat_token(ctx, RBRACKET, 1, FLINE))
+ return -1;
+
+ if (ctx->tok.tok != NEWLINE)
+ return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]");
+
+ return 0;
+}
+
+toml_table_t *toml_parse(char *conf, char *errbuf, int errbufsz) {
+ context_t ctx;
+
+ // clear errbuf
+ if (errbufsz <= 0)
+ errbufsz = 0;
+ if (errbufsz > 0)
+ errbuf[0] = 0;
+
+ // init context
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.start = conf;
+ ctx.stop = ctx.start + strlen(conf);
+ ctx.errbuf = errbuf;
+ ctx.errbufsz = errbufsz;
+
+ // start with an artificial newline of length 0
+ ctx.tok.tok = NEWLINE;
+ ctx.tok.lineno = 1;
+ ctx.tok.ptr = conf;
+ ctx.tok.len = 0;
+
+ // make a root table
+ if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) {
+ e_outofmemory(&ctx, FLINE);
+ // Do not goto fail, root table not set up yet
+ return 0;
+ }
+
+ // set root as default table
+ ctx.curtab = ctx.root;
+
+ /* Scan forward until EOF */
+ for (token_t tok = ctx.tok; !tok.eof; tok = ctx.tok) {
+ switch (tok.tok) {
+
+ case NEWLINE:
+ if (next_token(&ctx, 1))
+ goto fail;
+ break;
+
+ case STRING:
+ if (parse_keyval(&ctx, ctx.curtab))
+ goto fail;
+
+ if (ctx.tok.tok != NEWLINE) {
+ e_syntax(&ctx, ctx.tok.lineno, "extra chars after value");
+ goto fail;
+ }
+
+ if (eat_token(&ctx, NEWLINE, 1, FLINE))
+ goto fail;
+ break;
+
+ case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */
+ if (parse_select(&ctx))
+ goto fail;
+ break;
+
+ default:
+ e_syntax(&ctx, tok.lineno, "syntax error");
+ goto fail;
+ }
+ }
+
+ /* success */
+ for (int i = 0; i < ctx.tpath.top; i++)
+ xfree(ctx.tpath.key[i]);
+ return ctx.root;
+
+fail:
+ // Something bad has happened. Free resources and return error.
+ for (int i = 0; i < ctx.tpath.top; i++)
+ xfree(ctx.tpath.key[i]);
+ toml_free(ctx.root);
+ return 0;
+}
+
+toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz) {
+ int bufsz = 0;
+ char *buf = 0;
+ int off = 0;
+
+ /* read from fp into buf */
+ while (!feof(fp)) {
+
+ if (off == bufsz) {
+ int xsz = bufsz + 1000;
+ char *x = expand(buf, bufsz, xsz);
+ if (!x) {
+ snprintf(errbuf, errbufsz, "out of memory");
+ xfree(buf);
+ return 0;
+ }
+ buf = x;
+ bufsz = xsz;
+ }
+
+ errno = 0;
+ int n = fread(buf + off, 1, bufsz - off, fp);
+ if (ferror(fp)) {
+ snprintf(errbuf, errbufsz, "%s",
+ errno ? strerror(errno) : "Error reading file");
+ xfree(buf);
+ return 0;
+ }
+ off += n;
+ }
+
+ /* tag on a NUL to cap the string */
+ if (off == bufsz) {
+ int xsz = bufsz + 1;
+ char *x = expand(buf, bufsz, xsz);
+ if (!x) {
+ snprintf(errbuf, errbufsz, "out of memory");
+ xfree(buf);
+ return 0;
+ }
+ buf = x;
+ bufsz = xsz;
+ }
+ buf[off] = 0;
+
+ /* parse it, cleanup and finish */
+ toml_table_t *ret = toml_parse(buf, errbuf, errbufsz);
+ xfree(buf);
+ return ret;
+}
+
+static void xfree_kval(toml_keyval_t *p) {
+ if (!p)
+ return;
+ xfree(p->key);
+ xfree(p->val);
+ xfree(p);
+}
+
+static void xfree_tab(toml_table_t *p);
+
+static void xfree_arr(toml_array_t *p) {
+ if (!p)
+ return;
+
+ xfree(p->key);
+ const int n = p->nitem;
+ for (int i = 0; i < n; i++) {
+ toml_arritem_t *a = &p->item[i];
+ if (a->val)
+ xfree(a->val);
+ else if (a->arr)
+ xfree_arr(a->arr);
+ else if (a->tab)
+ xfree_tab(a->tab);
+ }
+ xfree(p->item);
+ xfree(p);
+}
+
+static void xfree_tab(toml_table_t *p) {
+ int i;
+
+ if (!p)
+ return;
+
+ xfree(p->key);
+
+ for (i = 0; i < p->nkval; i++)
+ xfree_kval(p->kval[i]);
+ xfree(p->kval);
+
+ for (i = 0; i < p->narr; i++)
+ xfree_arr(p->arr[i]);
+ xfree(p->arr);
+
+ for (i = 0; i < p->ntab; i++)
+ xfree_tab(p->tab[i]);
+ xfree(p->tab);
+
+ xfree(p);
+}
+
+void toml_free(toml_table_t *tab) { xfree_tab(tab); }
+
+static void set_token(context_t *ctx, tokentype_t tok, int lineno, char *ptr,
+ int len) {
+ token_t t;
+ t.tok = tok;
+ t.lineno = lineno;
+ t.ptr = ptr;
+ t.len = len;
+ t.eof = 0;
+ ctx->tok = t;
+}
+
+static void set_eof(context_t *ctx, int lineno) {
+ set_token(ctx, NEWLINE, lineno, ctx->stop, 0);
+ ctx->tok.eof = 1;
+}
+
+/* Scan p for n digits compositing entirely of [0-9] */
+static int scan_digits(const char *p, int n) {
+ int ret = 0;
+ for (; n > 0 && isdigit(*p); n--, p++) {
+ ret = 10 * ret + (*p - '0');
+ }
+ return n ? -1 : ret;
+}
+
+static int scan_date(const char *p, int *YY, int *MM, int *DD) {
+ int year, month, day;
+ year = scan_digits(p, 4);
+ month = (year >= 0 && p[4] == '-') ? scan_digits(p + 5, 2) : -1;
+ day = (month >= 0 && p[7] == '-') ? scan_digits(p + 8, 2) : -1;
+ if (YY)
+ *YY = year;
+ if (MM)
+ *MM = month;
+ if (DD)
+ *DD = day;
+ return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1;
+}
+
+static int scan_time(const char *p, int *hh, int *mm, int *ss) {
+ int hour, minute, second;
+ hour = scan_digits(p, 2);
+ minute = (hour >= 0 && p[2] == ':') ? scan_digits(p + 3, 2) : -1;
+ second = (minute >= 0 && p[5] == ':') ? scan_digits(p + 6, 2) : -1;
+ if (hh)
+ *hh = hour;
+ if (mm)
+ *mm = minute;
+ if (ss)
+ *ss = second;
+ return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1;
+}
+
+static int scan_string(context_t *ctx, char *p, int lineno, int dotisspecial) {
+ char *orig = p;
+ if (0 == strncmp(p, "'''", 3)) {
+ char *q = p + 3;
+
+ while (1) {
+ q = strstr(q, "'''");
+ if (0 == q) {
+ return e_syntax(ctx, lineno, "unterminated triple-s-quote");
+ }
+ while (q[3] == '\'')
+ q++;
+ break;
+ }
+
+ set_token(ctx, STRING, lineno, orig, q + 3 - orig);
+ return 0;
+ }
+
+ if (0 == strncmp(p, "\"\"\"", 3)) {
+ char *q = p + 3;
+
+ while (1) {
+ q = strstr(q, "\"\"\"");
+ if (0 == q) {
+ return e_syntax(ctx, lineno, "unterminated triple-d-quote");
+ }
+ if (q[-1] == '\\') {
+ q++;
+ continue;
+ }
+ while (q[3] == '\"')
+ q++;
+ break;
+ }
+
+ // the string is [p+3, q-1]
+
+ int hexreq = 0; /* #hex required */
+ int escape = 0;
+ for (p += 3; p < q; p++) {
+ if (escape) {
+ escape = 0;
+ if (strchr("btnfr\"\\", *p))
+ continue;
+ if (*p == 'u') {
+ hexreq = 4;
+ continue;
+ }
+ if (*p == 'U') {
+ hexreq = 8;
+ continue;
+ }
+ if (p[strspn(p, " \t\r")] == '\n')
+ continue; /* allow for line ending backslash */
+ return e_syntax(ctx, lineno, "bad escape char");
+ }
+ if (hexreq) {
+ hexreq--;
+ if (strchr("0123456789ABCDEF", *p))
+ continue;
+ return e_syntax(ctx, lineno, "expect hex char");
+ }
+ if (*p == '\\') {
+ escape = 1;
+ continue;
+ }
+ }
+ if (escape)
+ return e_syntax(ctx, lineno, "expect an escape char");
+ if (hexreq)
+ return e_syntax(ctx, lineno, "expected more hex char");
+
+ set_token(ctx, STRING, lineno, orig, q + 3 - orig);
+ return 0;
+ }
+
+ if ('\'' == *p) {
+ for (p++; *p && *p != '\n' && *p != '\''; p++)
+ ;
+ if (*p != '\'') {
+ return e_syntax(ctx, lineno, "unterminated s-quote");
+ }
+
+ set_token(ctx, STRING, lineno, orig, p + 1 - orig);
+ return 0;
+ }
+
+ if ('\"' == *p) {
+ int hexreq = 0; /* #hex required */
+ int escape = 0;
+ for (p++; *p; p++) {
+ if (escape) {
+ escape = 0;
+ if (strchr("btnfr\"\\", *p))
+ continue;
+ if (*p == 'u') {
+ hexreq = 4;
+ continue;
+ }
+ if (*p == 'U') {
+ hexreq = 8;
+ continue;
+ }
+ return e_syntax(ctx, lineno, "bad escape char");
+ }
+ if (hexreq) {
+ hexreq--;
+ if (strchr("0123456789ABCDEF", *p))
+ continue;
+ return e_syntax(ctx, lineno, "expect hex char");
+ }
+ if (*p == '\\') {
+ escape = 1;
+ continue;
+ }
+ if (*p == '\'') {
+ if (p[1] == '\'' && p[2] == '\'') {
+ return e_syntax(ctx, lineno, "triple-s-quote inside string lit");
+ }
+ continue;
+ }
+ if (*p == '\n')
+ break;
+ if (*p == '"')
+ break;
+ }
+ if (*p != '"') {
+ return e_syntax(ctx, lineno, "unterminated quote");
+ }
+
+ set_token(ctx, STRING, lineno, orig, p + 1 - orig);
+ return 0;
+ }
+
+ /* check for timestamp without quotes */
+ if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) {
+ // forward thru the timestamp
+ p += strspn(p, "0123456789.:+-T Z");
+ // squeeze out any spaces at end of string
+ for (; p[-1] == ' '; p--)
+ ;
+ // tokenize
+ set_token(ctx, STRING, lineno, orig, p - orig);
+ return 0;
+ }
+
+ /* literals */
+ for (; *p && *p != '\n'; p++) {
+ int ch = *p;
+ if (ch == '.' && dotisspecial)
+ break;
+ if ('A' <= ch && ch <= 'Z')
+ continue;
+ if ('a' <= ch && ch <= 'z')
+ continue;
+ if (strchr("0123456789+-_.", ch))
+ continue;
+ break;
+ }
+
+ set_token(ctx, STRING, lineno, orig, p - orig);
+ return 0;
+}
+
+static int next_token(context_t *ctx, int dotisspecial) {
+ int lineno = ctx->tok.lineno;
+ char *p = ctx->tok.ptr;
+ int i;
+
+ /* eat this tok */
+ for (i = 0; i < ctx->tok.len; i++) {
+ if (*p++ == '\n')
+ lineno++;
+ }
+
+ /* make next tok */
+ while (p < ctx->stop) {
+ /* skip comment. stop just before the \n. */
+ if (*p == '#') {
+ for (p++; p < ctx->stop && *p != '\n'; p++)
+ ;
+ continue;
+ }
+
+ if (dotisspecial && *p == '.') {
+ set_token(ctx, DOT, lineno, p, 1);
+ return 0;
+ }
+
+ switch (*p) {
+ case ',':
+ set_token(ctx, COMMA, lineno, p, 1);
+ return 0;
+ case '=':
+ set_token(ctx, EQUAL, lineno, p, 1);
+ return 0;
+ case '{':
+ set_token(ctx, LBRACE, lineno, p, 1);
+ return 0;
+ case '}':
+ set_token(ctx, RBRACE, lineno, p, 1);
+ return 0;
+ case '[':
+ set_token(ctx, LBRACKET, lineno, p, 1);
+ return 0;
+ case ']':
+ set_token(ctx, RBRACKET, lineno, p, 1);
+ return 0;
+ case '\n':
+ set_token(ctx, NEWLINE, lineno, p, 1);
+ return 0;
+ case '\r':
+ case ' ':
+ case '\t':
+ /* ignore white spaces */
+ p++;
+ continue;
+ }
+
+ return scan_string(ctx, p, lineno, dotisspecial);
+ }
+
+ set_eof(ctx, lineno);
+ return 0;
+}
+
+const char *toml_key_in(const toml_table_t *tab, int keyidx) {
+ if (keyidx < tab->nkval)
+ return tab->kval[keyidx]->key;
+
+ keyidx -= tab->nkval;
+ if (keyidx < tab->narr)
+ return tab->arr[keyidx]->key;
+
+ keyidx -= tab->narr;
+ if (keyidx < tab->ntab)
+ return tab->tab[keyidx]->key;
+
+ return 0;
+}
+
+int toml_key_exists(const toml_table_t *tab, const char *key) {
+ int i;
+ for (i = 0; i < tab->nkval; i++) {
+ if (0 == strcmp(key, tab->kval[i]->key))
+ return 1;
+ }
+ for (i = 0; i < tab->narr; i++) {
+ if (0 == strcmp(key, tab->arr[i]->key))
+ return 1;
+ }
+ for (i = 0; i < tab->ntab; i++) {
+ if (0 == strcmp(key, tab->tab[i]->key))
+ return 1;
+ }
+ return 0;
+}
+
+toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key) {
+ int i;
+ for (i = 0; i < tab->nkval; i++) {
+ if (0 == strcmp(key, tab->kval[i]->key))
+ return tab->kval[i]->val;
+ }
+ return 0;
+}
+
+toml_array_t *toml_array_in(const toml_table_t *tab, const char *key) {
+ int i;
+ for (i = 0; i < tab->narr; i++) {
+ if (0 == strcmp(key, tab->arr[i]->key))
+ return tab->arr[i];
+ }
+ return 0;
+}
+
+toml_table_t *toml_table_in(const toml_table_t *tab, const char *key) {
+ int i;
+ for (i = 0; i < tab->ntab; i++) {
+ if (0 == strcmp(key, tab->tab[i]->key))
+ return tab->tab[i];
+ }
+ return 0;
+}
+
+toml_raw_t toml_raw_at(const toml_array_t *arr, int idx) {
+ return (0 <= idx && idx < arr->nitem) ? arr->item[idx].val : 0;
+}
+
+char toml_array_kind(const toml_array_t *arr) { return arr->kind; }
+
+char toml_array_type(const toml_array_t *arr) {
+ if (arr->kind != 'v')
+ return 0;
+
+ if (arr->nitem == 0)
+ return 0;
+
+ return arr->type;
+}
+
+int toml_array_nelem(const toml_array_t *arr) { return arr->nitem; }
+
+const char *toml_array_key(const toml_array_t *arr) {
+ return arr ? arr->key : (const char *)NULL;
+}
+
+int toml_table_nkval(const toml_table_t *tab) { return tab->nkval; }
+
+int toml_table_narr(const toml_table_t *tab) { return tab->narr; }
+
+int toml_table_ntab(const toml_table_t *tab) { return tab->ntab; }
+
+const char *toml_table_key(const toml_table_t *tab) {
+ return tab ? tab->key : (const char *)NULL;
+}
+
+toml_array_t *toml_array_at(const toml_array_t *arr, int idx) {
+ return (0 <= idx && idx < arr->nitem) ? arr->item[idx].arr : 0;
+}
+
+toml_table_t *toml_table_at(const toml_array_t *arr, int idx) {
+ return (0 <= idx && idx < arr->nitem) ? arr->item[idx].tab : 0;
+}
+
+static int parse_millisec(const char *p, const char **endp);
+
+int toml_rtots(toml_raw_t src_, toml_timestamp_t *ret) {
+ if (!src_)
+ return -1;
+
+ const char *p = src_;
+ int must_parse_time = 0;
+
+ memset(ret, 0, sizeof(*ret));
+
+ int *year = &ret->__buffer.year;
+ int *month = &ret->__buffer.month;
+ int *day = &ret->__buffer.day;
+ int *hour = &ret->__buffer.hour;
+ int *minute = &ret->__buffer.minute;
+ int *second = &ret->__buffer.second;
+ int *millisec = &ret->__buffer.millisec;
+
+ /* parse date YYYY-MM-DD */
+ if (0 == scan_date(p, year, month, day)) {
+ ret->year = year;
+ ret->month = month;
+ ret->day = day;
+
+ p += 10;
+ if (*p) {
+ // parse the T or space separator
+ if (*p != 'T' && *p != ' ')
+ return -1;
+ must_parse_time = 1;
+ p++;
+ }
+ }
+
+ /* parse time HH:MM:SS */
+ if (0 == scan_time(p, hour, minute, second)) {
+ ret->hour = hour;
+ ret->minute = minute;
+ ret->second = second;
+
+ /* optionally, parse millisec */
+ p += 8;
+ if (*p == '.') {
+ p++; /* skip '.' */
+ const char *qq;
+ *millisec = parse_millisec(p, &qq);
+ ret->millisec = millisec;
+ p = qq;
+ }
+
+ if (*p) {
+ /* parse and copy Z */
+ char *z = ret->__buffer.z;
+ ret->z = z;
+ if (*p == 'Z' || *p == 'z') {
+ *z++ = 'Z';
+ p++;
+ *z = 0;
+
+ } else if (*p == '+' || *p == '-') {
+ *z++ = *p++;
+
+ if (!(isdigit(p[0]) && isdigit(p[1])))
+ return -1;
+ *z++ = *p++;
+ *z++ = *p++;
+
+ if (*p == ':') {
+ *z++ = *p++;
+
+ if (!(isdigit(p[0]) && isdigit(p[1])))
+ return -1;
+ *z++ = *p++;
+ *z++ = *p++;
+ }
+
+ *z = 0;
+ }
+ }
+ }
+ if (*p != 0)
+ return -1;
+
+ if (must_parse_time && !ret->hour)
+ return -1;
+
+ return 0;
+}
+
+/* Raw to boolean */
+int toml_rtob(toml_raw_t src, int *ret_) {
+ if (!src)
+ return -1;
+ int dummy;
+ int *ret = ret_ ? ret_ : &dummy;
+
+ if (0 == strcmp(src, "true")) {
+ *ret = 1;
+ return 0;
+ }
+ if (0 == strcmp(src, "false")) {
+ *ret = 0;
+ return 0;
+ }
+ return -1;
+}
+
+/* Raw to integer */
+int toml_rtoi(toml_raw_t src, int64_t *ret_) {
+ if (!src)
+ return -1;
+
+ char buf[100];
+ char *p = buf;
+ char *q = p + sizeof(buf);
+ const char *s = src;
+ int base = 0;
+ int64_t dummy;
+ int64_t *ret = ret_ ? ret_ : &dummy;
+
+ /* allow +/- */
+ if (s[0] == '+' || s[0] == '-')
+ *p++ = *s++;
+
+ /* disallow +_100 */
+ if (s[0] == '_')
+ return -1;
+
+ /* if 0* ... */
+ if ('0' == s[0]) {
+ switch (s[1]) {
+ case 'x':
+ base = 16;
+ s += 2;
+ break;
+ case 'o':
+ base = 8;
+ s += 2;
+ break;
+ case 'b':
+ base = 2;
+ s += 2;
+ break;
+ case '\0':
+ return *ret = 0, 0;
+ default:
+ /* ensure no other digits after it */
+ if (s[1])
+ return -1;
+ }
+ }
+
+ /* just strip underscores and pass to strtoll */
+ while (*s && p < q) {
+ int ch = *s++;
+ if (ch == '_') {
+ // disallow '__'
+ if (s[0] == '_')
+ return -1;
+ // numbers cannot end with '_'
+ if (s[0] == '\0')
+ return -1;
+ continue; /* skip _ */
+ }
+ *p++ = ch;
+ }
+
+ // if not at end-of-string or we ran out of buffer ...
+ if (*s || p == q)
+ return -1;
+
+ /* cap with NUL */
+ *p = 0;
+
+ /* Run strtoll on buf to get the integer */
+ char *endp;
+ errno = 0;
+ *ret = strtoll(buf, &endp, base);
+ return (errno || *endp) ? -1 : 0;
+}
+
+int toml_rtod_ex(toml_raw_t src, double *ret_, char *buf, int buflen) {
+ if (!src)
+ return -1;
+
+ char *p = buf;
+ char *q = p + buflen;
+ const char *s = src;
+ double dummy;
+ double *ret = ret_ ? ret_ : &dummy;
+
+ /* allow +/- */
+ if (s[0] == '+' || s[0] == '-')
+ *p++ = *s++;
+
+ /* disallow +_1.00 */
+ if (s[0] == '_')
+ return -1;
+
+ /* decimal point, if used, must be surrounded by at least one digit on each
+ * side */
+ {
+ char *dot = strchr(s, '.');
+ if (dot) {
+ if (dot == s || !isdigit(dot[-1]) || !isdigit(dot[1]))
+ return -1;
+ }
+ }
+
+ /* zero must be followed by . or 'e', or NUL */
+ if (s[0] == '0' && s[1] && !strchr("eE.", s[1]))
+ return -1;
+
+ /* just strip underscores and pass to strtod */
+ while (*s && p < q) {
+ int ch = *s++;
+ if (ch == '_') {
+ // disallow '__'
+ if (s[0] == '_')
+ return -1;
+ // disallow last char '_'
+ if (s[0] == 0)
+ return -1;
+ continue; /* skip _ */
+ }
+ *p++ = ch;
+ }
+ if (*s || p == q)
+ return -1; /* reached end of string or buffer is full? */
+
+ /* cap with NUL */
+ *p = 0;
+
+ /* Run strtod on buf to get the value */
+ char *endp;
+ errno = 0;
+ *ret = strtod(buf, &endp);
+ return (errno || *endp) ? -1 : 0;
+}
+
+int toml_rtod(toml_raw_t src, double *ret_) {
+ char buf[100];
+ return toml_rtod_ex(src, ret_, buf, sizeof(buf));
+}
+
+int toml_rtos(toml_raw_t src, char **ret) {
+ int multiline = 0;
+ const char *sp;
+ const char *sq;
+
+ *ret = 0;
+ if (!src)
+ return -1;
+
+ int qchar = src[0];
+ int srclen = strlen(src);
+ if (!(qchar == '\'' || qchar == '"')) {
+ return -1;
+ }
+
+ // triple quotes?
+ if (qchar == src[1] && qchar == src[2]) {
+ multiline = 1;
+ sp = src + 3;
+ sq = src + srclen - 3;
+ /* last 3 chars in src must be qchar */
+ if (!(sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar))
+ return -1;
+
+ /* skip new line immediate after qchar */
+ if (sp[0] == '\n')
+ sp++;
+ else if (sp[0] == '\r' && sp[1] == '\n')
+ sp += 2;
+
+ } else {
+ sp = src + 1;
+ sq = src + srclen - 1;
+ /* last char in src must be qchar */
+ if (!(sp <= sq && *sq == qchar))
+ return -1;
+ }
+
+ if (qchar == '\'') {
+ *ret = norm_lit_str(sp, sq - sp, multiline, 0, 0);
+ } else {
+ *ret = norm_basic_str(sp, sq - sp, multiline, 0, 0);
+ }
+
+ return *ret ? 0 : -1;
+}
+
+toml_datum_t toml_string_at(const toml_array_t *arr, int idx) {
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtos(toml_raw_at(arr, idx), &ret.u.s));
+ return ret;
+}
+
+toml_datum_t toml_bool_at(const toml_array_t *arr, int idx) {
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtob(toml_raw_at(arr, idx), &ret.u.b));
+ return ret;
+}
+
+toml_datum_t toml_int_at(const toml_array_t *arr, int idx) {
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtoi(toml_raw_at(arr, idx), &ret.u.i));
+ return ret;
+}
+
+toml_datum_t toml_double_at(const toml_array_t *arr, int idx) {
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtod(toml_raw_at(arr, idx), &ret.u.d));
+ return ret;
+}
+
+toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx) {
+ toml_timestamp_t ts;
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtots(toml_raw_at(arr, idx), &ts));
+ if (ret.ok) {
+ ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts)));
+ if (ret.ok) {
+ *ret.u.ts = ts;
+ if (ret.u.ts->year)
+ ret.u.ts->year = &ret.u.ts->__buffer.year;
+ if (ret.u.ts->month)
+ ret.u.ts->month = &ret.u.ts->__buffer.month;
+ if (ret.u.ts->day)
+ ret.u.ts->day = &ret.u.ts->__buffer.day;
+ if (ret.u.ts->hour)
+ ret.u.ts->hour = &ret.u.ts->__buffer.hour;
+ if (ret.u.ts->minute)
+ ret.u.ts->minute = &ret.u.ts->__buffer.minute;
+ if (ret.u.ts->second)
+ ret.u.ts->second = &ret.u.ts->__buffer.second;
+ if (ret.u.ts->millisec)
+ ret.u.ts->millisec = &ret.u.ts->__buffer.millisec;
+ if (ret.u.ts->z)
+ ret.u.ts->z = ret.u.ts->__buffer.z;
+ }
+ }
+ return ret;
+}
+
+toml_datum_t toml_string_in(const toml_table_t *arr, const char *key) {
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ toml_raw_t raw = toml_raw_in(arr, key);
+ if (raw) {
+ ret.ok = (0 == toml_rtos(raw, &ret.u.s));
+ }
+ return ret;
+}
+
+toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key) {
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtob(toml_raw_in(arr, key), &ret.u.b));
+ return ret;
+}
+
+toml_datum_t toml_int_in(const toml_table_t *arr, const char *key) {
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtoi(toml_raw_in(arr, key), &ret.u.i));
+ return ret;
+}
+
+toml_datum_t toml_double_in(const toml_table_t *arr, const char *key) {
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtod(toml_raw_in(arr, key), &ret.u.d));
+ return ret;
+}
+
+toml_datum_t toml_timestamp_in(const toml_table_t *arr, const char *key) {
+ toml_timestamp_t ts;
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtots(toml_raw_in(arr, key), &ts));
+ if (ret.ok) {
+ ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts)));
+ if (ret.ok) {
+ *ret.u.ts = ts;
+ if (ret.u.ts->year)
+ ret.u.ts->year = &ret.u.ts->__buffer.year;
+ if (ret.u.ts->month)
+ ret.u.ts->month = &ret.u.ts->__buffer.month;
+ if (ret.u.ts->day)
+ ret.u.ts->day = &ret.u.ts->__buffer.day;
+ if (ret.u.ts->hour)
+ ret.u.ts->hour = &ret.u.ts->__buffer.hour;
+ if (ret.u.ts->minute)
+ ret.u.ts->minute = &ret.u.ts->__buffer.minute;
+ if (ret.u.ts->second)
+ ret.u.ts->second = &ret.u.ts->__buffer.second;
+ if (ret.u.ts->millisec)
+ ret.u.ts->millisec = &ret.u.ts->__buffer.millisec;
+ if (ret.u.ts->z)
+ ret.u.ts->z = ret.u.ts->__buffer.z;
+ }
+ }
+ return ret;
+}
+
+static int parse_millisec(const char *p, const char **endp) {
+ int ret = 0;
+ int unit = 100; /* unit in millisec */
+ for (; '0' <= *p && *p <= '9'; p++, unit /= 10) {
+ ret += (*p - '0') * unit;
+ }
+ *endp = p;
+ return ret;
+}
diff --git a/deps/toml/toml.h b/deps/toml/toml.h
new file mode 100644
index 0000000..19dc3d2
--- /dev/null
+++ b/deps/toml/toml.h
@@ -0,0 +1,175 @@
+/*
+ MIT License
+
+ Copyright (c) CK Tan
+ https://github.com/cktan/tomlc99
+
+ 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 TOML_H
+#define TOML_H
+
+#ifdef _MSC_VER
+#pragma warning(disable: 4996)
+#endif
+
+#include <stdint.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+#define TOML_EXTERN extern "C"
+#else
+#define TOML_EXTERN extern
+#endif
+
+typedef struct toml_timestamp_t toml_timestamp_t;
+typedef struct toml_table_t toml_table_t;
+typedef struct toml_array_t toml_array_t;
+typedef struct toml_datum_t toml_datum_t;
+
+/* Parse a file. Return a table on success, or 0 otherwise.
+ * Caller must toml_free(the-return-value) after use.
+ */
+TOML_EXTERN toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz);
+
+/* Parse a string containing the full config.
+ * Return a table on success, or 0 otherwise.
+ * Caller must toml_free(the-return-value) after use.
+ */
+TOML_EXTERN toml_table_t *toml_parse(char *conf, /* NUL terminated, please. */
+ char *errbuf, int errbufsz);
+
+/* Free the table returned by toml_parse() or toml_parse_file(). Once
+ * this function is called, any handles accessed through this tab
+ * directly or indirectly are no longer valid.
+ */
+TOML_EXTERN void toml_free(toml_table_t *tab);
+
+/* Timestamp types. The year, month, day, hour, minute, second, z
+ * fields may be NULL if they are not relevant. e.g. In a DATE
+ * type, the hour, minute, second and z fields will be NULLs.
+ */
+struct toml_timestamp_t {
+ struct { /* internal. do not use. */
+ int year, month, day;
+ int hour, minute, second, millisec;
+ char z[10];
+ } __buffer;
+ int *year, *month, *day;
+ int *hour, *minute, *second, *millisec;
+ char *z;
+};
+
+/*-----------------------------------------------------------------
+ * Enhanced access methods
+ */
+struct toml_datum_t {
+ int ok;
+ union {
+ toml_timestamp_t *ts; /* ts must be freed after use */
+ char *s; /* string value. s must be freed after use */
+ int b; /* bool value */
+ int64_t i; /* int value */
+ double d; /* double value */
+ } u;
+};
+
+/* on arrays: */
+/* ... retrieve size of array. */
+TOML_EXTERN int toml_array_nelem(const toml_array_t *arr);
+/* ... retrieve values using index. */
+TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t *arr, int idx);
+TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t *arr, int idx);
+TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t *arr, int idx);
+TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t *arr, int idx);
+TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx);
+/* ... retrieve array or table using index. */
+TOML_EXTERN toml_array_t *toml_array_at(const toml_array_t *arr, int idx);
+TOML_EXTERN toml_table_t *toml_table_at(const toml_array_t *arr, int idx);
+
+/* on tables: */
+/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
+TOML_EXTERN const char *toml_key_in(const toml_table_t *tab, int keyidx);
+/* ... returns 1 if key exists in tab, 0 otherwise */
+TOML_EXTERN int toml_key_exists(const toml_table_t *tab, const char *key);
+/* ... retrieve values using key. */
+TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t *arr,
+ const char *key);
+TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key);
+TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t *arr, const char *key);
+TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t *arr,
+ const char *key);
+TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t *arr,
+ const char *key);
+/* .. retrieve array or table using key. */
+TOML_EXTERN toml_array_t *toml_array_in(const toml_table_t *tab,
+ const char *key);
+TOML_EXTERN toml_table_t *toml_table_in(const toml_table_t *tab,
+ const char *key);
+
+/*-----------------------------------------------------------------
+ * lesser used
+ */
+/* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */
+TOML_EXTERN char toml_array_kind(const toml_array_t *arr);
+
+/* For array kind 'v'alue, return the type of values
+ i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed
+ 0 if unknown
+*/
+TOML_EXTERN char toml_array_type(const toml_array_t *arr);
+
+/* Return the key of an array */
+TOML_EXTERN const char *toml_array_key(const toml_array_t *arr);
+
+/* Return the number of key-values in a table */
+TOML_EXTERN int toml_table_nkval(const toml_table_t *tab);
+
+/* Return the number of arrays in a table */
+TOML_EXTERN int toml_table_narr(const toml_table_t *tab);
+
+/* Return the number of sub-tables in a table */
+TOML_EXTERN int toml_table_ntab(const toml_table_t *tab);
+
+/* Return the key of a table*/
+TOML_EXTERN const char *toml_table_key(const toml_table_t *tab);
+
+/*--------------------------------------------------------------
+ * misc
+ */
+TOML_EXTERN int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret);
+TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
+TOML_EXTERN void toml_set_memutil(void *(*xxmalloc)(size_t),
+ void (*xxfree)(void *));
+
+/*--------------------------------------------------------------
+ * deprecated
+ */
+/* A raw value, must be processed by toml_rto* before using. */
+typedef const char *toml_raw_t;
+TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key);
+TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t *arr, int idx);
+TOML_EXTERN int toml_rtos(toml_raw_t s, char **ret);
+TOML_EXTERN int toml_rtob(toml_raw_t s, int *ret);
+TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t *ret);
+TOML_EXTERN int toml_rtod(toml_raw_t s, double *ret);
+TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double *ret, char *buf, int buflen);
+TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t *ret);
+
+#endif /* TOML_H */
diff --git a/deps/uthash/utarray.h b/deps/uthash/utarray.h
new file mode 100644
index 0000000..dc1cf8e
--- /dev/null
+++ b/deps/uthash/utarray.h
@@ -0,0 +1,248 @@
+/*
+Copyright (c) 2008-2022, Troy D. Hanson https://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* a dynamic array implementation using macros
+ */
+#ifndef UTARRAY_H
+#define UTARRAY_H
+
+#define UTARRAY_VERSION 2.3.0
+
+#include <stddef.h> /* size_t */
+#include <string.h> /* memset, etc */
+#include <stdlib.h> /* exit */
+
+#ifdef __GNUC__
+#define UTARRAY_UNUSED __attribute__((__unused__))
+#else
+#define UTARRAY_UNUSED
+#endif
+
+#ifdef oom
+#error "The name of macro 'oom' has been changed to 'utarray_oom'. Please update your code."
+#define utarray_oom() oom()
+#endif
+
+#ifndef utarray_oom
+#define utarray_oom() exit(-1)
+#endif
+
+typedef void (ctor_f)(void *dst, const void *src);
+typedef void (dtor_f)(void *elt);
+typedef void (init_f)(void *elt);
+typedef struct {
+ size_t sz;
+ init_f *init;
+ ctor_f *copy;
+ dtor_f *dtor;
+} UT_icd;
+
+typedef struct {
+ unsigned i,n;/* i: index of next available slot, n: num slots */
+ UT_icd icd; /* initializer, copy and destructor functions */
+ char *d; /* n slots of size icd->sz*/
+} UT_array;
+
+#define utarray_init(a,_icd) do { \
+ memset(a,0,sizeof(UT_array)); \
+ (a)->icd = *(_icd); \
+} while(0)
+
+#define utarray_done(a) do { \
+ if ((a)->n) { \
+ if ((a)->icd.dtor) { \
+ unsigned _ut_i; \
+ for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
+ (a)->icd.dtor(utarray_eltptr(a,_ut_i)); \
+ } \
+ } \
+ free((a)->d); \
+ } \
+ (a)->n=0; \
+} while(0)
+
+#define utarray_new(a,_icd) do { \
+ (a) = (UT_array*)malloc(sizeof(UT_array)); \
+ if ((a) == NULL) { \
+ utarray_oom(); \
+ } \
+ utarray_init(a,_icd); \
+} while(0)
+
+#define utarray_free(a) do { \
+ utarray_done(a); \
+ free(a); \
+} while(0)
+
+#define utarray_reserve(a,by) do { \
+ if (((a)->i+(by)) > (a)->n) { \
+ char *utarray_tmp; \
+ while (((a)->i+(by)) > (a)->n) { (a)->n = ((a)->n ? (2*(a)->n) : 8); } \
+ utarray_tmp=(char*)realloc((a)->d, (a)->n*(a)->icd.sz); \
+ if (utarray_tmp == NULL) { \
+ utarray_oom(); \
+ } \
+ (a)->d=utarray_tmp; \
+ } \
+} while(0)
+
+#define utarray_push_back(a,p) do { \
+ utarray_reserve(a,1); \
+ if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,(a)->i++), p); } \
+ else { memcpy(_utarray_eltptr(a,(a)->i++), p, (a)->icd.sz); }; \
+} while(0)
+
+#define utarray_pop_back(a) do { \
+ if ((a)->icd.dtor) { (a)->icd.dtor( _utarray_eltptr(a,--((a)->i))); } \
+ else { (a)->i--; } \
+} while(0)
+
+#define utarray_extend_back(a) do { \
+ utarray_reserve(a,1); \
+ if ((a)->icd.init) { (a)->icd.init(_utarray_eltptr(a,(a)->i)); } \
+ else { memset(_utarray_eltptr(a,(a)->i),0,(a)->icd.sz); } \
+ (a)->i++; \
+} while(0)
+
+#define utarray_len(a) ((a)->i)
+
+#define utarray_eltptr(a,j) (((j) < (a)->i) ? _utarray_eltptr(a,j) : NULL)
+#define _utarray_eltptr(a,j) ((void*)((a)->d + ((a)->icd.sz * (j))))
+
+#define utarray_insert(a,p,j) do { \
+ if ((j) > (a)->i) utarray_resize(a,j); \
+ utarray_reserve(a,1); \
+ if ((j) < (a)->i) { \
+ memmove( _utarray_eltptr(a,(j)+1), _utarray_eltptr(a,j), \
+ ((a)->i - (j))*((a)->icd.sz)); \
+ } \
+ if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,j), p); } \
+ else { memcpy(_utarray_eltptr(a,j), p, (a)->icd.sz); }; \
+ (a)->i++; \
+} while(0)
+
+#define utarray_inserta(a,w,j) do { \
+ if (utarray_len(w) == 0) break; \
+ if ((j) > (a)->i) utarray_resize(a,j); \
+ utarray_reserve(a,utarray_len(w)); \
+ if ((j) < (a)->i) { \
+ memmove(_utarray_eltptr(a,(j)+utarray_len(w)), \
+ _utarray_eltptr(a,j), \
+ ((a)->i - (j))*((a)->icd.sz)); \
+ } \
+ if ((a)->icd.copy) { \
+ unsigned _ut_i; \
+ for(_ut_i=0;_ut_i<(w)->i;_ut_i++) { \
+ (a)->icd.copy(_utarray_eltptr(a, (j) + _ut_i), _utarray_eltptr(w, _ut_i)); \
+ } \
+ } else { \
+ memcpy(_utarray_eltptr(a,j), _utarray_eltptr(w,0), \
+ utarray_len(w)*((a)->icd.sz)); \
+ } \
+ (a)->i += utarray_len(w); \
+} while(0)
+
+#define utarray_resize(dst,num) do { \
+ unsigned _ut_i; \
+ if ((dst)->i > (unsigned)(num)) { \
+ if ((dst)->icd.dtor) { \
+ for (_ut_i = (num); _ut_i < (dst)->i; ++_ut_i) { \
+ (dst)->icd.dtor(_utarray_eltptr(dst, _ut_i)); \
+ } \
+ } \
+ } else if ((dst)->i < (unsigned)(num)) { \
+ utarray_reserve(dst, (num) - (dst)->i); \
+ if ((dst)->icd.init) { \
+ for (_ut_i = (dst)->i; _ut_i < (unsigned)(num); ++_ut_i) { \
+ (dst)->icd.init(_utarray_eltptr(dst, _ut_i)); \
+ } \
+ } else { \
+ memset(_utarray_eltptr(dst, (dst)->i), 0, (dst)->icd.sz*((num) - (dst)->i)); \
+ } \
+ } \
+ (dst)->i = (num); \
+} while(0)
+
+#define utarray_concat(dst,src) do { \
+ utarray_inserta(dst, src, utarray_len(dst)); \
+} while(0)
+
+#define utarray_erase(a,pos,len) do { \
+ if ((a)->icd.dtor) { \
+ unsigned _ut_i; \
+ for (_ut_i = 0; _ut_i < (len); _ut_i++) { \
+ (a)->icd.dtor(utarray_eltptr(a, (pos) + _ut_i)); \
+ } \
+ } \
+ if ((a)->i > ((pos) + (len))) { \
+ memmove(_utarray_eltptr(a, pos), _utarray_eltptr(a, (pos) + (len)), \
+ ((a)->i - ((pos) + (len))) * (a)->icd.sz); \
+ } \
+ (a)->i -= (len); \
+} while(0)
+
+#define utarray_renew(a,u) do { \
+ if (a) utarray_clear(a); \
+ else utarray_new(a, u); \
+} while(0)
+
+#define utarray_clear(a) do { \
+ if ((a)->i > 0) { \
+ if ((a)->icd.dtor) { \
+ unsigned _ut_i; \
+ for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
+ (a)->icd.dtor(_utarray_eltptr(a, _ut_i)); \
+ } \
+ } \
+ (a)->i = 0; \
+ } \
+} while(0)
+
+#define utarray_sort(a,cmp) do { \
+ qsort((a)->d, (a)->i, (a)->icd.sz, cmp); \
+} while(0)
+
+#define utarray_find(a,v,cmp) bsearch((v),(a)->d,(a)->i,(a)->icd.sz,cmp)
+
+#define utarray_front(a) (((a)->i) ? (_utarray_eltptr(a,0)) : NULL)
+#define utarray_next(a,e) (((e)==NULL) ? utarray_front(a) : (((a)->i != utarray_eltidx(a,e)+1) ? _utarray_eltptr(a,utarray_eltidx(a,e)+1) : NULL))
+#define utarray_prev(a,e) (((e)==NULL) ? utarray_back(a) : ((utarray_eltidx(a,e) != 0) ? _utarray_eltptr(a,utarray_eltidx(a,e)-1) : NULL))
+#define utarray_back(a) (((a)->i) ? (_utarray_eltptr(a,(a)->i-1)) : NULL)
+#define utarray_eltidx(a,e) (((char*)(e) - (a)->d) / (a)->icd.sz)
+
+/* last we pre-define a few icd for common utarrays of ints and strings */
+static void utarray_str_cpy(void *dst, const void *src) {
+ char *const *srcc = (char *const *)src;
+ char **dstc = (char**)dst;
+ *dstc = (*srcc == NULL) ? NULL : strdup(*srcc);
+}
+static void utarray_str_dtor(void *elt) {
+ char **eltc = (char**)elt;
+ if (*eltc != NULL) free(*eltc);
+}
+static const UT_icd ut_str_icd UTARRAY_UNUSED = {sizeof(char*),NULL,utarray_str_cpy,utarray_str_dtor};
+static const UT_icd ut_int_icd UTARRAY_UNUSED = {sizeof(int),NULL,NULL,NULL};
+static const UT_icd ut_ptr_icd UTARRAY_UNUSED = {sizeof(void*),NULL,NULL,NULL};
+
+
+#endif /* UTARRAY_H */
diff --git a/deps/uthash/uthash.h b/deps/uthash/uthash.h
new file mode 100644
index 0000000..49c69df
--- /dev/null
+++ b/deps/uthash/uthash.h
@@ -0,0 +1,1138 @@
+/*
+Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTHASH_H
+#define UTHASH_H
+
+#define UTHASH_VERSION 2.3.0
+
+#include <string.h> /* memcmp, memset, strlen */
+#include <stddef.h> /* ptrdiff_t */
+#include <stdlib.h> /* exit */
+
+#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT
+/* This codepath is provided for backward compatibility, but I plan to remove it. */
+#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead"
+typedef unsigned int uint32_t;
+typedef unsigned char uint8_t;
+#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT
+#else
+#include <stdint.h> /* uint8_t, uint32_t */
+#endif
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+ As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+ when compiling c++ source) this code uses whatever method is needed
+ or, for VS2008 where neither is available, uses casting workarounds. */
+#if !defined(DECLTYPE) && !defined(NO_DECLTYPE)
+#if defined(_MSC_VER) /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */
+#define DECLTYPE(x) (decltype(x))
+#else /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#endif
+#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#else /* GNU, Sun and other compilers */
+#define DECLTYPE(x) (__typeof(x))
+#endif
+#endif
+
+#ifdef NO_DECLTYPE
+#define DECLTYPE(x)
+#define DECLTYPE_ASSIGN(dst,src) \
+do { \
+ char **_da_dst = (char**)(&(dst)); \
+ *_da_dst = (char*)(src); \
+} while (0)
+#else
+#define DECLTYPE_ASSIGN(dst,src) \
+do { \
+ (dst) = DECLTYPE(dst)(src); \
+} while (0)
+#endif
+
+#ifndef uthash_malloc
+#define uthash_malloc(sz) malloc(sz) /* malloc fcn */
+#endif
+#ifndef uthash_free
+#define uthash_free(ptr,sz) free(ptr) /* free fcn */
+#endif
+#ifndef uthash_bzero
+#define uthash_bzero(a,n) memset(a,'\0',n)
+#endif
+#ifndef uthash_strlen
+#define uthash_strlen(s) strlen(s)
+#endif
+
+#ifndef HASH_FUNCTION
+#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv)
+#endif
+
+#ifndef HASH_KEYCMP
+#define HASH_KEYCMP(a,b,n) memcmp(a,b,n)
+#endif
+
+#ifndef uthash_noexpand_fyi
+#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */
+#endif
+#ifndef uthash_expand_fyi
+#define uthash_expand_fyi(tbl) /* can be defined to log expands */
+#endif
+
+#ifndef HASH_NONFATAL_OOM
+#define HASH_NONFATAL_OOM 0
+#endif
+
+#if HASH_NONFATAL_OOM
+/* malloc failures can be recovered from */
+
+#ifndef uthash_nonfatal_oom
+#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */
+#endif
+
+#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0)
+#define IF_HASH_NONFATAL_OOM(x) x
+
+#else
+/* malloc failures result in lost memory, hash tables are unusable */
+
+#ifndef uthash_fatal
+#define uthash_fatal(msg) exit(-1) /* fatal OOM error */
+#endif
+
+#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory")
+#define IF_HASH_NONFATAL_OOM(x)
+
+#endif
+
+/* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */
+#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */
+
+/* calculate the element whose hash handle address is hhp */
+#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
+/* calculate the hash handle from element address elp */
+#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho)))
+
+#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \
+do { \
+ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \
+ unsigned _hd_bkt; \
+ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \
+ (head)->hh.tbl->buckets[_hd_bkt].count++; \
+ _hd_hh_item->hh_next = NULL; \
+ _hd_hh_item->hh_prev = NULL; \
+} while (0)
+
+#define HASH_VALUE(keyptr,keylen,hashv) \
+do { \
+ HASH_FUNCTION(keyptr, keylen, hashv); \
+} while (0)
+
+#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \
+do { \
+ (out) = NULL; \
+ if (head) { \
+ unsigned _hf_bkt; \
+ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \
+ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \
+ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \
+ } \
+ } \
+} while (0)
+
+#define HASH_FIND(hh,head,keyptr,keylen,out) \
+do { \
+ (out) = NULL; \
+ if (head) { \
+ unsigned _hf_hashv; \
+ HASH_VALUE(keyptr, keylen, _hf_hashv); \
+ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \
+ } \
+} while (0)
+
+#ifdef HASH_BLOOM
+#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM)
+#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL)
+#define HASH_BLOOM_MAKE(tbl,oomed) \
+do { \
+ (tbl)->bloom_nbits = HASH_BLOOM; \
+ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \
+ if (!(tbl)->bloom_bv) { \
+ HASH_RECORD_OOM(oomed); \
+ } else { \
+ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \
+ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \
+ } \
+} while (0)
+
+#define HASH_BLOOM_FREE(tbl) \
+do { \
+ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \
+} while (0)
+
+#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U)))
+#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U)))
+
+#define HASH_BLOOM_ADD(tbl,hashv) \
+ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))
+
+#define HASH_BLOOM_TEST(tbl,hashv) \
+ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))
+
+#else
+#define HASH_BLOOM_MAKE(tbl,oomed)
+#define HASH_BLOOM_FREE(tbl)
+#define HASH_BLOOM_ADD(tbl,hashv)
+#define HASH_BLOOM_TEST(tbl,hashv) (1)
+#define HASH_BLOOM_BYTELEN 0U
+#endif
+
+#define HASH_MAKE_TABLE(hh,head,oomed) \
+do { \
+ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \
+ if (!(head)->hh.tbl) { \
+ HASH_RECORD_OOM(oomed); \
+ } else { \
+ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head)->hh.tbl->tail = &((head)->hh); \
+ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \
+ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \
+ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \
+ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \
+ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \
+ (head)->hh.tbl->signature = HASH_SIGNATURE; \
+ if (!(head)->hh.tbl->buckets) { \
+ HASH_RECORD_OOM(oomed); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ } else { \
+ uthash_bzero((head)->hh.tbl->buckets, \
+ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \
+ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \
+ IF_HASH_NONFATAL_OOM( \
+ if (oomed) { \
+ uthash_free((head)->hh.tbl->buckets, \
+ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ } \
+ ) \
+ } \
+ } \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \
+do { \
+ (replaced) = NULL; \
+ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+ if (replaced) { \
+ HASH_DELETE(hh, head, replaced); \
+ } \
+ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \
+do { \
+ (replaced) = NULL; \
+ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+ if (replaced) { \
+ HASH_DELETE(hh, head, replaced); \
+ } \
+ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \
+} while (0)
+
+#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \
+do { \
+ unsigned _hr_hashv; \
+ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \
+ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \
+} while (0)
+
+#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \
+do { \
+ unsigned _hr_hashv; \
+ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \
+ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \
+} while (0)
+
+#define HASH_APPEND_LIST(hh, head, add) \
+do { \
+ (add)->hh.next = NULL; \
+ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \
+ (head)->hh.tbl->tail->next = (add); \
+ (head)->hh.tbl->tail = &((add)->hh); \
+} while (0)
+
+#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \
+do { \
+ do { \
+ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \
+ break; \
+ } \
+ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \
+} while (0)
+
+#ifdef NO_DECLTYPE
+#undef HASH_AKBI_INNER_LOOP
+#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \
+do { \
+ char *_hs_saved_head = (char*)(head); \
+ do { \
+ DECLTYPE_ASSIGN(head, _hs_iter); \
+ if (cmpfcn(head, add) > 0) { \
+ DECLTYPE_ASSIGN(head, _hs_saved_head); \
+ break; \
+ } \
+ DECLTYPE_ASSIGN(head, _hs_saved_head); \
+ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \
+} while (0)
+#endif
+
+#if HASH_NONFATAL_OOM
+
+#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \
+do { \
+ if (!(oomed)) { \
+ unsigned _ha_bkt; \
+ (head)->hh.tbl->num_items++; \
+ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \
+ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \
+ if (oomed) { \
+ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \
+ HASH_DELETE_HH(hh, head, &(add)->hh); \
+ (add)->hh.tbl = NULL; \
+ uthash_nonfatal_oom(add); \
+ } else { \
+ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \
+ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \
+ } \
+ } else { \
+ (add)->hh.tbl = NULL; \
+ uthash_nonfatal_oom(add); \
+ } \
+} while (0)
+
+#else
+
+#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \
+do { \
+ unsigned _ha_bkt; \
+ (head)->hh.tbl->num_items++; \
+ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \
+ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \
+ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \
+ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \
+} while (0)
+
+#endif
+
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \
+do { \
+ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \
+ (add)->hh.hashv = (hashval); \
+ (add)->hh.key = (char*) (keyptr); \
+ (add)->hh.keylen = (unsigned) (keylen_in); \
+ if (!(head)) { \
+ (add)->hh.next = NULL; \
+ (add)->hh.prev = NULL; \
+ HASH_MAKE_TABLE(hh, add, _ha_oomed); \
+ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \
+ (head) = (add); \
+ IF_HASH_NONFATAL_OOM( } ) \
+ } else { \
+ void *_hs_iter = (head); \
+ (add)->hh.tbl = (head)->hh.tbl; \
+ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \
+ if (_hs_iter) { \
+ (add)->hh.next = _hs_iter; \
+ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \
+ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \
+ } else { \
+ (head) = (add); \
+ } \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \
+ } else { \
+ HASH_APPEND_LIST(hh, head, add); \
+ } \
+ } \
+ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \
+ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \
+} while (0)
+
+#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \
+do { \
+ unsigned _hs_hashv; \
+ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \
+ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \
+ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn)
+
+#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \
+ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn)
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \
+do { \
+ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \
+ (add)->hh.hashv = (hashval); \
+ (add)->hh.key = (const void*) (keyptr); \
+ (add)->hh.keylen = (unsigned) (keylen_in); \
+ if (!(head)) { \
+ (add)->hh.next = NULL; \
+ (add)->hh.prev = NULL; \
+ HASH_MAKE_TABLE(hh, add, _ha_oomed); \
+ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \
+ (head) = (add); \
+ IF_HASH_NONFATAL_OOM( } ) \
+ } else { \
+ (add)->hh.tbl = (head)->hh.tbl; \
+ HASH_APPEND_LIST(hh, head, add); \
+ } \
+ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \
+ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \
+} while (0)
+
+#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \
+do { \
+ unsigned _ha_hashv; \
+ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \
+ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \
+ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add)
+
+#define HASH_ADD(hh,head,fieldname,keylen_in,add) \
+ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add)
+
+#define HASH_TO_BKT(hashv,num_bkts,bkt) \
+do { \
+ bkt = ((hashv) & ((num_bkts) - 1U)); \
+} while (0)
+
+/* delete "delptr" from the hash table.
+ * "the usual" patch-up process for the app-order doubly-linked-list.
+ * The use of _hd_hh_del below deserves special explanation.
+ * These used to be expressed using (delptr) but that led to a bug
+ * if someone used the same symbol for the head and deletee, like
+ * HASH_DELETE(hh,users,users);
+ * We want that to work, but by changing the head (users) below
+ * we were forfeiting our ability to further refer to the deletee (users)
+ * in the patch-up process. Solution: use scratch space to
+ * copy the deletee pointer, then the latter references are via that
+ * scratch pointer rather than through the repointed (users) symbol.
+ */
+#define HASH_DELETE(hh,head,delptr) \
+ HASH_DELETE_HH(hh, head, &(delptr)->hh)
+
+#define HASH_DELETE_HH(hh,head,delptrhh) \
+do { \
+ struct UT_hash_handle *_hd_hh_del = (delptrhh); \
+ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \
+ HASH_BLOOM_FREE((head)->hh.tbl); \
+ uthash_free((head)->hh.tbl->buckets, \
+ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head) = NULL; \
+ } else { \
+ unsigned _hd_bkt; \
+ if (_hd_hh_del == (head)->hh.tbl->tail) { \
+ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \
+ } \
+ if (_hd_hh_del->prev != NULL) { \
+ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \
+ } else { \
+ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \
+ } \
+ if (_hd_hh_del->next != NULL) { \
+ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \
+ } \
+ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \
+ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \
+ (head)->hh.tbl->num_items--; \
+ } \
+ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \
+} while (0)
+
+/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */
+#define HASH_FIND_STR(head,findstr,out) \
+do { \
+ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \
+ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \
+} while (0)
+#define HASH_ADD_STR(head,strfield,add) \
+do { \
+ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \
+ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \
+} while (0)
+#define HASH_REPLACE_STR(head,strfield,add,replaced) \
+do { \
+ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \
+ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \
+} while (0)
+#define HASH_FIND_INT(head,findint,out) \
+ HASH_FIND(hh,head,findint,sizeof(int),out)
+#define HASH_ADD_INT(head,intfield,add) \
+ HASH_ADD(hh,head,intfield,sizeof(int),add)
+#define HASH_REPLACE_INT(head,intfield,add,replaced) \
+ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)
+#define HASH_FIND_PTR(head,findptr,out) \
+ HASH_FIND(hh,head,findptr,sizeof(void *),out)
+#define HASH_ADD_PTR(head,ptrfield,add) \
+ HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
+#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \
+ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)
+#define HASH_DEL(head,delptr) \
+ HASH_DELETE(hh,head,delptr)
+
+/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.
+ * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.
+ */
+#ifdef HASH_DEBUG
+#include <stdio.h> /* fprintf, stderr */
+#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0)
+#define HASH_FSCK(hh,head,where) \
+do { \
+ struct UT_hash_handle *_thh; \
+ if (head) { \
+ unsigned _bkt_i; \
+ unsigned _count = 0; \
+ char *_prev; \
+ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \
+ unsigned _bkt_count = 0; \
+ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \
+ _prev = NULL; \
+ while (_thh) { \
+ if (_prev != (char*)(_thh->hh_prev)) { \
+ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \
+ (where), (void*)_thh->hh_prev, (void*)_prev); \
+ } \
+ _bkt_count++; \
+ _prev = (char*)(_thh); \
+ _thh = _thh->hh_next; \
+ } \
+ _count += _bkt_count; \
+ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \
+ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \
+ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \
+ } \
+ } \
+ if (_count != (head)->hh.tbl->num_items) { \
+ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \
+ (where), (head)->hh.tbl->num_items, _count); \
+ } \
+ _count = 0; \
+ _prev = NULL; \
+ _thh = &(head)->hh; \
+ while (_thh) { \
+ _count++; \
+ if (_prev != (char*)_thh->prev) { \
+ HASH_OOPS("%s: invalid prev %p, actual %p\n", \
+ (where), (void*)_thh->prev, (void*)_prev); \
+ } \
+ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \
+ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \
+ } \
+ if (_count != (head)->hh.tbl->num_items) { \
+ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \
+ (where), (head)->hh.tbl->num_items, _count); \
+ } \
+ } \
+} while (0)
+#else
+#define HASH_FSCK(hh,head,where)
+#endif
+
+/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to
+ * the descriptor to which this macro is defined for tuning the hash function.
+ * The app can #include <unistd.h> to get the prototype for write(2). */
+#ifdef HASH_EMIT_KEYS
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \
+do { \
+ unsigned _klen = fieldlen; \
+ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \
+ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \
+} while (0)
+#else
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)
+#endif
+
+/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */
+#define HASH_BER(key,keylen,hashv) \
+do { \
+ unsigned _hb_keylen = (unsigned)keylen; \
+ const unsigned char *_hb_key = (const unsigned char*)(key); \
+ (hashv) = 0; \
+ while (_hb_keylen-- != 0U) { \
+ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \
+ } \
+} while (0)
+
+
+/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at
+ * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx
+ * (archive link: https://archive.is/Ivcan )
+ */
+#define HASH_SAX(key,keylen,hashv) \
+do { \
+ unsigned _sx_i; \
+ const unsigned char *_hs_key = (const unsigned char*)(key); \
+ hashv = 0; \
+ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \
+ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \
+ } \
+} while (0)
+/* FNV-1a variation */
+#define HASH_FNV(key,keylen,hashv) \
+do { \
+ unsigned _fn_i; \
+ const unsigned char *_hf_key = (const unsigned char*)(key); \
+ (hashv) = 2166136261U; \
+ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \
+ hashv = hashv ^ _hf_key[_fn_i]; \
+ hashv = hashv * 16777619U; \
+ } \
+} while (0)
+
+#define HASH_OAT(key,keylen,hashv) \
+do { \
+ unsigned _ho_i; \
+ const unsigned char *_ho_key=(const unsigned char*)(key); \
+ hashv = 0; \
+ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \
+ hashv += _ho_key[_ho_i]; \
+ hashv += (hashv << 10); \
+ hashv ^= (hashv >> 6); \
+ } \
+ hashv += (hashv << 3); \
+ hashv ^= (hashv >> 11); \
+ hashv += (hashv << 15); \
+} while (0)
+
+#define HASH_JEN_MIX(a,b,c) \
+do { \
+ a -= b; a -= c; a ^= ( c >> 13 ); \
+ b -= c; b -= a; b ^= ( a << 8 ); \
+ c -= a; c -= b; c ^= ( b >> 13 ); \
+ a -= b; a -= c; a ^= ( c >> 12 ); \
+ b -= c; b -= a; b ^= ( a << 16 ); \
+ c -= a; c -= b; c ^= ( b >> 5 ); \
+ a -= b; a -= c; a ^= ( c >> 3 ); \
+ b -= c; b -= a; b ^= ( a << 10 ); \
+ c -= a; c -= b; c ^= ( b >> 15 ); \
+} while (0)
+
+#define HASH_JEN(key,keylen,hashv) \
+do { \
+ unsigned _hj_i,_hj_j,_hj_k; \
+ unsigned const char *_hj_key=(unsigned const char*)(key); \
+ hashv = 0xfeedbeefu; \
+ _hj_i = _hj_j = 0x9e3779b9u; \
+ _hj_k = (unsigned)(keylen); \
+ while (_hj_k >= 12U) { \
+ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \
+ + ( (unsigned)_hj_key[2] << 16 ) \
+ + ( (unsigned)_hj_key[3] << 24 ) ); \
+ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \
+ + ( (unsigned)_hj_key[6] << 16 ) \
+ + ( (unsigned)_hj_key[7] << 24 ) ); \
+ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \
+ + ( (unsigned)_hj_key[10] << 16 ) \
+ + ( (unsigned)_hj_key[11] << 24 ) ); \
+ \
+ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \
+ \
+ _hj_key += 12; \
+ _hj_k -= 12U; \
+ } \
+ hashv += (unsigned)(keylen); \
+ switch ( _hj_k ) { \
+ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \
+ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \
+ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \
+ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \
+ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \
+ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \
+ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \
+ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \
+ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \
+ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \
+ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \
+ default: ; \
+ } \
+ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \
+} while (0)
+
+/* The Paul Hsieh hash function */
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \
+ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \
+ +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+#define HASH_SFH(key,keylen,hashv) \
+do { \
+ unsigned const char *_sfh_key=(unsigned const char*)(key); \
+ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \
+ \
+ unsigned _sfh_rem = _sfh_len & 3U; \
+ _sfh_len >>= 2; \
+ hashv = 0xcafebabeu; \
+ \
+ /* Main loop */ \
+ for (;_sfh_len > 0U; _sfh_len--) { \
+ hashv += get16bits (_sfh_key); \
+ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \
+ hashv = (hashv << 16) ^ _sfh_tmp; \
+ _sfh_key += 2U*sizeof (uint16_t); \
+ hashv += hashv >> 11; \
+ } \
+ \
+ /* Handle end cases */ \
+ switch (_sfh_rem) { \
+ case 3: hashv += get16bits (_sfh_key); \
+ hashv ^= hashv << 16; \
+ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \
+ hashv += hashv >> 11; \
+ break; \
+ case 2: hashv += get16bits (_sfh_key); \
+ hashv ^= hashv << 11; \
+ hashv += hashv >> 17; \
+ break; \
+ case 1: hashv += *_sfh_key; \
+ hashv ^= hashv << 10; \
+ hashv += hashv >> 1; \
+ break; \
+ default: ; \
+ } \
+ \
+ /* Force "avalanching" of final 127 bits */ \
+ hashv ^= hashv << 3; \
+ hashv += hashv >> 5; \
+ hashv ^= hashv << 4; \
+ hashv += hashv >> 17; \
+ hashv ^= hashv << 25; \
+ hashv += hashv >> 6; \
+} while (0)
+
+/* iterate over items in a known bucket to find desired item */
+#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \
+do { \
+ if ((head).hh_head != NULL) { \
+ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \
+ } else { \
+ (out) = NULL; \
+ } \
+ while ((out) != NULL) { \
+ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \
+ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \
+ break; \
+ } \
+ } \
+ if ((out)->hh.hh_next != NULL) { \
+ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \
+ } else { \
+ (out) = NULL; \
+ } \
+ } \
+} while (0)
+
+/* add an item to a bucket */
+#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \
+do { \
+ UT_hash_bucket *_ha_head = &(head); \
+ _ha_head->count++; \
+ (addhh)->hh_next = _ha_head->hh_head; \
+ (addhh)->hh_prev = NULL; \
+ if (_ha_head->hh_head != NULL) { \
+ _ha_head->hh_head->hh_prev = (addhh); \
+ } \
+ _ha_head->hh_head = (addhh); \
+ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \
+ && !(addhh)->tbl->noexpand) { \
+ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \
+ IF_HASH_NONFATAL_OOM( \
+ if (oomed) { \
+ HASH_DEL_IN_BKT(head,addhh); \
+ } \
+ ) \
+ } \
+} while (0)
+
+/* remove an item from a given bucket */
+#define HASH_DEL_IN_BKT(head,delhh) \
+do { \
+ UT_hash_bucket *_hd_head = &(head); \
+ _hd_head->count--; \
+ if (_hd_head->hh_head == (delhh)) { \
+ _hd_head->hh_head = (delhh)->hh_next; \
+ } \
+ if ((delhh)->hh_prev) { \
+ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \
+ } \
+ if ((delhh)->hh_next) { \
+ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \
+ } \
+} while (0)
+
+/* Bucket expansion has the effect of doubling the number of buckets
+ * and redistributing the items into the new buckets. Ideally the
+ * items will distribute more or less evenly into the new buckets
+ * (the extent to which this is true is a measure of the quality of
+ * the hash function as it applies to the key domain).
+ *
+ * With the items distributed into more buckets, the chain length
+ * (item count) in each bucket is reduced. Thus by expanding buckets
+ * the hash keeps a bound on the chain length. This bounded chain
+ * length is the essence of how a hash provides constant time lookup.
+ *
+ * The calculation of tbl->ideal_chain_maxlen below deserves some
+ * explanation. First, keep in mind that we're calculating the ideal
+ * maximum chain length based on the *new* (doubled) bucket count.
+ * In fractions this is just n/b (n=number of items,b=new num buckets).
+ * Since the ideal chain length is an integer, we want to calculate
+ * ceil(n/b). We don't depend on floating point arithmetic in this
+ * hash, so to calculate ceil(n/b) with integers we could write
+ *
+ * ceil(n/b) = (n/b) + ((n%b)?1:0)
+ *
+ * and in fact a previous version of this hash did just that.
+ * But now we have improved things a bit by recognizing that b is
+ * always a power of two. We keep its base 2 log handy (call it lb),
+ * so now we can write this with a bit shift and logical AND:
+ *
+ * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
+ *
+ */
+#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \
+do { \
+ unsigned _he_bkt; \
+ unsigned _he_bkt_i; \
+ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \
+ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \
+ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \
+ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \
+ if (!_he_new_buckets) { \
+ HASH_RECORD_OOM(oomed); \
+ } else { \
+ uthash_bzero(_he_new_buckets, \
+ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \
+ (tbl)->ideal_chain_maxlen = \
+ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \
+ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \
+ (tbl)->nonideal_items = 0; \
+ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \
+ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \
+ while (_he_thh != NULL) { \
+ _he_hh_nxt = _he_thh->hh_next; \
+ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \
+ _he_newbkt = &(_he_new_buckets[_he_bkt]); \
+ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \
+ (tbl)->nonideal_items++; \
+ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \
+ _he_newbkt->expand_mult++; \
+ } \
+ } \
+ _he_thh->hh_prev = NULL; \
+ _he_thh->hh_next = _he_newbkt->hh_head; \
+ if (_he_newbkt->hh_head != NULL) { \
+ _he_newbkt->hh_head->hh_prev = _he_thh; \
+ } \
+ _he_newbkt->hh_head = _he_thh; \
+ _he_thh = _he_hh_nxt; \
+ } \
+ } \
+ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \
+ (tbl)->num_buckets *= 2U; \
+ (tbl)->log2_num_buckets++; \
+ (tbl)->buckets = _he_new_buckets; \
+ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \
+ ((tbl)->ineff_expands+1U) : 0U; \
+ if ((tbl)->ineff_expands > 1U) { \
+ (tbl)->noexpand = 1; \
+ uthash_noexpand_fyi(tbl); \
+ } \
+ uthash_expand_fyi(tbl); \
+ } \
+} while (0)
+
+
+/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */
+/* Note that HASH_SORT assumes the hash handle name to be hh.
+ * HASH_SRT was added to allow the hash handle name to be passed in. */
+#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)
+#define HASH_SRT(hh,head,cmpfcn) \
+do { \
+ unsigned _hs_i; \
+ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \
+ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \
+ if (head != NULL) { \
+ _hs_insize = 1; \
+ _hs_looping = 1; \
+ _hs_list = &((head)->hh); \
+ while (_hs_looping != 0U) { \
+ _hs_p = _hs_list; \
+ _hs_list = NULL; \
+ _hs_tail = NULL; \
+ _hs_nmerges = 0; \
+ while (_hs_p != NULL) { \
+ _hs_nmerges++; \
+ _hs_q = _hs_p; \
+ _hs_psize = 0; \
+ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \
+ _hs_psize++; \
+ _hs_q = ((_hs_q->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \
+ if (_hs_q == NULL) { \
+ break; \
+ } \
+ } \
+ _hs_qsize = _hs_insize; \
+ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \
+ if (_hs_psize == 0U) { \
+ _hs_e = _hs_q; \
+ _hs_q = ((_hs_q->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \
+ _hs_qsize--; \
+ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \
+ _hs_e = _hs_p; \
+ if (_hs_p != NULL) { \
+ _hs_p = ((_hs_p->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \
+ } \
+ _hs_psize--; \
+ } else if ((cmpfcn( \
+ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \
+ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \
+ )) <= 0) { \
+ _hs_e = _hs_p; \
+ if (_hs_p != NULL) { \
+ _hs_p = ((_hs_p->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \
+ } \
+ _hs_psize--; \
+ } else { \
+ _hs_e = _hs_q; \
+ _hs_q = ((_hs_q->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \
+ _hs_qsize--; \
+ } \
+ if ( _hs_tail != NULL ) { \
+ _hs_tail->next = ((_hs_e != NULL) ? \
+ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \
+ } else { \
+ _hs_list = _hs_e; \
+ } \
+ if (_hs_e != NULL) { \
+ _hs_e->prev = ((_hs_tail != NULL) ? \
+ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \
+ } \
+ _hs_tail = _hs_e; \
+ } \
+ _hs_p = _hs_q; \
+ } \
+ if (_hs_tail != NULL) { \
+ _hs_tail->next = NULL; \
+ } \
+ if (_hs_nmerges <= 1U) { \
+ _hs_looping = 0; \
+ (head)->hh.tbl->tail = _hs_tail; \
+ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \
+ } \
+ _hs_insize *= 2U; \
+ } \
+ HASH_FSCK(hh, head, "HASH_SRT"); \
+ } \
+} while (0)
+
+/* This function selects items from one hash into another hash.
+ * The end result is that the selected items have dual presence
+ * in both hashes. There is no copy of the items made; rather
+ * they are added into the new hash through a secondary hash
+ * hash handle that must be present in the structure. */
+#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \
+do { \
+ unsigned _src_bkt, _dst_bkt; \
+ void *_last_elt = NULL, *_elt; \
+ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \
+ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \
+ if ((src) != NULL) { \
+ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \
+ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \
+ _src_hh != NULL; \
+ _src_hh = _src_hh->hh_next) { \
+ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \
+ if (cond(_elt)) { \
+ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \
+ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \
+ _dst_hh->key = _src_hh->key; \
+ _dst_hh->keylen = _src_hh->keylen; \
+ _dst_hh->hashv = _src_hh->hashv; \
+ _dst_hh->prev = _last_elt; \
+ _dst_hh->next = NULL; \
+ if (_last_elt_hh != NULL) { \
+ _last_elt_hh->next = _elt; \
+ } \
+ if ((dst) == NULL) { \
+ DECLTYPE_ASSIGN(dst, _elt); \
+ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \
+ IF_HASH_NONFATAL_OOM( \
+ if (_hs_oomed) { \
+ uthash_nonfatal_oom(_elt); \
+ (dst) = NULL; \
+ continue; \
+ } \
+ ) \
+ } else { \
+ _dst_hh->tbl = (dst)->hh_dst.tbl; \
+ } \
+ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \
+ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \
+ (dst)->hh_dst.tbl->num_items++; \
+ IF_HASH_NONFATAL_OOM( \
+ if (_hs_oomed) { \
+ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \
+ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \
+ _dst_hh->tbl = NULL; \
+ uthash_nonfatal_oom(_elt); \
+ continue; \
+ } \
+ ) \
+ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \
+ _last_elt = _elt; \
+ _last_elt_hh = _dst_hh; \
+ } \
+ } \
+ } \
+ } \
+ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \
+} while (0)
+
+#define HASH_CLEAR(hh,head) \
+do { \
+ if ((head) != NULL) { \
+ HASH_BLOOM_FREE((head)->hh.tbl); \
+ uthash_free((head)->hh.tbl->buckets, \
+ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head) = NULL; \
+ } \
+} while (0)
+
+#define HASH_OVERHEAD(hh,head) \
+ (((head) != NULL) ? ( \
+ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \
+ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \
+ sizeof(UT_hash_table) + \
+ (HASH_BLOOM_BYTELEN))) : 0U)
+
+#ifdef NO_DECLTYPE
+#define HASH_ITER(hh,head,el,tmp) \
+for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \
+ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#else
+#define HASH_ITER(hh,head,el,tmp) \
+for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \
+ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#endif
+
+/* obtain a count of items in the hash */
+#define HASH_COUNT(head) HASH_CNT(hh,head)
+#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)
+
+typedef struct UT_hash_bucket {
+ struct UT_hash_handle *hh_head;
+ unsigned count;
+
+ /* expand_mult is normally set to 0. In this situation, the max chain length
+ * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If
+ * the bucket's chain exceeds this length, bucket expansion is triggered).
+ * However, setting expand_mult to a non-zero value delays bucket expansion
+ * (that would be triggered by additions to this particular bucket)
+ * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.
+ * (The multiplier is simply expand_mult+1). The whole idea of this
+ * multiplier is to reduce bucket expansions, since they are expensive, in
+ * situations where we know that a particular bucket tends to be overused.
+ * It is better to let its chain length grow to a longer yet-still-bounded
+ * value, than to do an O(n) bucket expansion too often.
+ */
+ unsigned expand_mult;
+
+} UT_hash_bucket;
+
+/* random signature used only to find hash tables in external analysis */
+#define HASH_SIGNATURE 0xa0111fe1u
+#define HASH_BLOOM_SIGNATURE 0xb12220f2u
+
+typedef struct UT_hash_table {
+ UT_hash_bucket *buckets;
+ unsigned num_buckets, log2_num_buckets;
+ unsigned num_items;
+ struct UT_hash_handle *tail; /* tail hh in app order, for fast append */
+ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
+
+ /* in an ideal situation (all buckets used equally), no bucket would have
+ * more than ceil(#items/#buckets) items. that's the ideal chain length. */
+ unsigned ideal_chain_maxlen;
+
+ /* nonideal_items is the number of items in the hash whose chain position
+ * exceeds the ideal chain maxlen. these items pay the penalty for an uneven
+ * hash distribution; reaching them in a chain traversal takes >ideal steps */
+ unsigned nonideal_items;
+
+ /* ineffective expands occur when a bucket doubling was performed, but
+ * afterward, more than half the items in the hash had nonideal chain
+ * positions. If this happens on two consecutive expansions we inhibit any
+ * further expansion, as it's not helping; this happens when the hash
+ * function isn't a good fit for the key domain. When expansion is inhibited
+ * the hash will still work, albeit no longer in constant time. */
+ unsigned ineff_expands, noexpand;
+
+ uint32_t signature; /* used only to find hash tables in external analysis */
+#ifdef HASH_BLOOM
+ uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
+ uint8_t *bloom_bv;
+ uint8_t bloom_nbits;
+#endif
+
+} UT_hash_table;
+
+typedef struct UT_hash_handle {
+ struct UT_hash_table *tbl;
+ void *prev; /* prev element in app order */
+ void *next; /* next element in app order */
+ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */
+ struct UT_hash_handle *hh_next; /* next hh in bucket order */
+ const void *key; /* ptr to enclosing struct's key */
+ unsigned keylen; /* enclosing struct's key len */
+ unsigned hashv; /* result of hash-fcn(key) */
+} UT_hash_handle;
+
+#endif /* UTHASH_H */
diff --git a/deps/uthash/utlist.h b/deps/uthash/utlist.h
new file mode 100644
index 0000000..492908c
--- /dev/null
+++ b/deps/uthash/utlist.h
@@ -0,0 +1,1073 @@
+/*
+Copyright (c) 2007-2022, Troy D. Hanson https://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTLIST_H
+#define UTLIST_H
+
+#define UTLIST_VERSION 2.3.0
+
+#include <assert.h>
+
+/*
+ * This file contains macros to manipulate singly and doubly-linked lists.
+ *
+ * 1. LL_ macros: singly-linked lists.
+ * 2. DL_ macros: doubly-linked lists.
+ * 3. CDL_ macros: circular doubly-linked lists.
+ *
+ * To use singly-linked lists, your structure must have a "next" pointer.
+ * To use doubly-linked lists, your structure must "prev" and "next" pointers.
+ * Either way, the pointer to the head of the list must be initialized to NULL.
+ *
+ * ----------------.EXAMPLE -------------------------
+ * struct item {
+ * int id;
+ * struct item *prev, *next;
+ * }
+ *
+ * struct item *list = NULL:
+ *
+ * int main() {
+ * struct item *item;
+ * ... allocate and populate item ...
+ * DL_APPEND(list, item);
+ * }
+ * --------------------------------------------------
+ *
+ * For doubly-linked lists, the append and delete macros are O(1)
+ * For singly-linked lists, append and delete are O(n) but prepend is O(1)
+ * The sort macro is O(n log(n)) for all types of single/double/circular lists.
+ */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+ As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+ when compiling c++ source) this code uses whatever method is needed
+ or, for VS2008 where neither is available, uses casting workarounds. */
+#if !defined(LDECLTYPE) && !defined(NO_DECLTYPE)
+#if defined(_MSC_VER) /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */
+#define LDECLTYPE(x) decltype(x)
+#else /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#endif
+#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#else /* GNU, Sun and other compilers */
+#define LDECLTYPE(x) __typeof(x)
+#endif
+#endif
+
+/* for VS2008 we use some workarounds to get around the lack of decltype,
+ * namely, we always reassign our tmp variable to the list head if we need
+ * to dereference its prev/next pointers, and save/restore the real head.*/
+#ifdef NO_DECLTYPE
+#define IF_NO_DECLTYPE(x) x
+#define LDECLTYPE(x) char*
+#define UTLIST_SV(elt,list) _tmp = (char*)(list); {char **_alias = (char**)&(list); *_alias = (elt); }
+#define UTLIST_NEXT(elt,list,next) ((char*)((list)->next))
+#define UTLIST_NEXTASGN(elt,list,to,next) { char **_alias = (char**)&((list)->next); *_alias=(char*)(to); }
+/* #define UTLIST_PREV(elt,list,prev) ((char*)((list)->prev)) */
+#define UTLIST_PREVASGN(elt,list,to,prev) { char **_alias = (char**)&((list)->prev); *_alias=(char*)(to); }
+#define UTLIST_RS(list) { char **_alias = (char**)&(list); *_alias=_tmp; }
+#define UTLIST_CASTASGN(a,b) { char **_alias = (char**)&(a); *_alias=(char*)(b); }
+#else
+#define IF_NO_DECLTYPE(x)
+#define UTLIST_SV(elt,list)
+#define UTLIST_NEXT(elt,list,next) ((elt)->next)
+#define UTLIST_NEXTASGN(elt,list,to,next) ((elt)->next)=(to)
+/* #define UTLIST_PREV(elt,list,prev) ((elt)->prev) */
+#define UTLIST_PREVASGN(elt,list,to,prev) ((elt)->prev)=(to)
+#define UTLIST_RS(list)
+#define UTLIST_CASTASGN(a,b) (a)=(b)
+#endif
+
+/******************************************************************************
+ * The sort macro is an adaptation of Simon Tatham's O(n log(n)) mergesort *
+ * Unwieldy variable names used here to avoid shadowing passed-in variables. *
+ *****************************************************************************/
+#define LL_SORT(list, cmp) \
+ LL_SORT2(list, cmp, next)
+
+#define LL_SORT2(list, cmp, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;) \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ UTLIST_CASTASGN(_ls_p,list); \
+ (list) = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ } else { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ } \
+ if (_ls_tail) { \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+ } else { \
+ UTLIST_CASTASGN(list,_ls_e); \
+ } \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ if (_ls_tail) { \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list); \
+ } \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+
+#define DL_SORT(list, cmp) \
+ DL_SORT2(list, cmp, prev, next)
+
+#define DL_SORT2(list, cmp, prev, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ IF_NO_DECLTYPE(LDECLTYPE(list) _tmp;) \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ UTLIST_CASTASGN(_ls_p,list); \
+ (list) = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ UTLIST_SV(_ls_q,list); _ls_q = UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while ((_ls_psize > 0) || ((_ls_qsize > 0) && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ } else if ((_ls_qsize == 0) || (!_ls_q)) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ } else { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ } \
+ if (_ls_tail) { \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+ } else { \
+ UTLIST_CASTASGN(list,_ls_e); \
+ } \
+ UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list); \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ UTLIST_CASTASGN((list)->prev, _ls_tail); \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,NULL,next); UTLIST_RS(list); \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+#define CDL_SORT(list, cmp) \
+ CDL_SORT2(list, cmp, prev, next)
+
+#define CDL_SORT2(list, cmp, prev, next) \
+do { \
+ LDECLTYPE(list) _ls_p; \
+ LDECLTYPE(list) _ls_q; \
+ LDECLTYPE(list) _ls_e; \
+ LDECLTYPE(list) _ls_tail; \
+ LDECLTYPE(list) _ls_oldhead; \
+ LDECLTYPE(list) _tmp; \
+ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \
+ if (list) { \
+ _ls_insize = 1; \
+ _ls_looping = 1; \
+ while (_ls_looping) { \
+ UTLIST_CASTASGN(_ls_p,list); \
+ UTLIST_CASTASGN(_ls_oldhead,list); \
+ (list) = NULL; \
+ _ls_tail = NULL; \
+ _ls_nmerges = 0; \
+ while (_ls_p) { \
+ _ls_nmerges++; \
+ _ls_q = _ls_p; \
+ _ls_psize = 0; \
+ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \
+ _ls_psize++; \
+ UTLIST_SV(_ls_q,list); \
+ if (UTLIST_NEXT(_ls_q,list,next) == _ls_oldhead) { \
+ _ls_q = NULL; \
+ } else { \
+ _ls_q = UTLIST_NEXT(_ls_q,list,next); \
+ } \
+ UTLIST_RS(list); \
+ if (!_ls_q) break; \
+ } \
+ _ls_qsize = _ls_insize; \
+ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \
+ if (_ls_psize == 0) { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \
+ } else if (_ls_qsize == 0 || !_ls_q) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \
+ } else if (cmp(_ls_p,_ls_q) <= 0) { \
+ _ls_e = _ls_p; UTLIST_SV(_ls_p,list); _ls_p = \
+ UTLIST_NEXT(_ls_p,list,next); UTLIST_RS(list); _ls_psize--; \
+ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \
+ } else { \
+ _ls_e = _ls_q; UTLIST_SV(_ls_q,list); _ls_q = \
+ UTLIST_NEXT(_ls_q,list,next); UTLIST_RS(list); _ls_qsize--; \
+ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \
+ } \
+ if (_ls_tail) { \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_ls_e,next); UTLIST_RS(list); \
+ } else { \
+ UTLIST_CASTASGN(list,_ls_e); \
+ } \
+ UTLIST_SV(_ls_e,list); UTLIST_PREVASGN(_ls_e,list,_ls_tail,prev); UTLIST_RS(list); \
+ _ls_tail = _ls_e; \
+ } \
+ _ls_p = _ls_q; \
+ } \
+ UTLIST_CASTASGN((list)->prev,_ls_tail); \
+ UTLIST_CASTASGN(_tmp,list); \
+ UTLIST_SV(_ls_tail,list); UTLIST_NEXTASGN(_ls_tail,list,_tmp,next); UTLIST_RS(list); \
+ if (_ls_nmerges <= 1) { \
+ _ls_looping=0; \
+ } \
+ _ls_insize *= 2; \
+ } \
+ } \
+} while (0)
+
+/******************************************************************************
+ * singly linked list macros (non-circular) *
+ *****************************************************************************/
+#define LL_PREPEND(head,add) \
+ LL_PREPEND2(head,add,next)
+
+#define LL_PREPEND2(head,add,next) \
+do { \
+ (add)->next = (head); \
+ (head) = (add); \
+} while (0)
+
+#define LL_CONCAT(head1,head2) \
+ LL_CONCAT2(head1,head2,next)
+
+#define LL_CONCAT2(head1,head2,next) \
+do { \
+ LDECLTYPE(head1) _tmp; \
+ if (head1) { \
+ _tmp = (head1); \
+ while (_tmp->next) { _tmp = _tmp->next; } \
+ _tmp->next=(head2); \
+ } else { \
+ (head1)=(head2); \
+ } \
+} while (0)
+
+#define LL_APPEND(head,add) \
+ LL_APPEND2(head,add,next)
+
+#define LL_APPEND2(head,add,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ (add)->next=NULL; \
+ if (head) { \
+ _tmp = (head); \
+ while (_tmp->next) { _tmp = _tmp->next; } \
+ _tmp->next=(add); \
+ } else { \
+ (head)=(add); \
+ } \
+} while (0)
+
+#define LL_INSERT_INORDER(head,add,cmp) \
+ LL_INSERT_INORDER2(head,add,cmp,next)
+
+#define LL_INSERT_INORDER2(head,add,cmp,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if (head) { \
+ LL_LOWER_BOUND2(head, _tmp, add, cmp, next); \
+ LL_APPEND_ELEM2(head, _tmp, add, next); \
+ } else { \
+ (head) = (add); \
+ (head)->next = NULL; \
+ } \
+} while (0)
+
+#define LL_LOWER_BOUND(head,elt,like,cmp) \
+ LL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define LL_LOWER_BOUND2(head,elt,like,cmp,next) \
+ do { \
+ if ((head) == NULL || (cmp(head, like)) >= 0) { \
+ (elt) = NULL; \
+ } else { \
+ for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) { \
+ if (cmp((elt)->next, like) >= 0) { \
+ break; \
+ } \
+ } \
+ } \
+ } while (0)
+
+#define LL_DELETE(head,del) \
+ LL_DELETE2(head,del,next)
+
+#define LL_DELETE2(head,del,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if ((head) == (del)) { \
+ (head)=(head)->next; \
+ } else { \
+ _tmp = (head); \
+ while (_tmp->next && (_tmp->next != (del))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (del)->next; \
+ } \
+ } \
+} while (0)
+
+#define LL_COUNT(head,el,counter) \
+ LL_COUNT2(head,el,counter,next) \
+
+#define LL_COUNT2(head,el,counter,next) \
+do { \
+ (counter) = 0; \
+ LL_FOREACH2(head,el,next) { ++(counter); } \
+} while (0)
+
+#define LL_FOREACH(head,el) \
+ LL_FOREACH2(head,el,next)
+
+#define LL_FOREACH2(head,el,next) \
+ for ((el) = (head); el; (el) = (el)->next)
+
+#define LL_FOREACH_SAFE(head,el,tmp) \
+ LL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define LL_FOREACH_SAFE2(head,el,tmp,next) \
+ for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp))
+
+#define LL_SEARCH_SCALAR(head,out,field,val) \
+ LL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define LL_SEARCH_SCALAR2(head,out,field,val,next) \
+do { \
+ LL_FOREACH2(head,out,next) { \
+ if ((out)->field == (val)) break; \
+ } \
+} while (0)
+
+#define LL_SEARCH(head,out,elt,cmp) \
+ LL_SEARCH2(head,out,elt,cmp,next)
+
+#define LL_SEARCH2(head,out,elt,cmp,next) \
+do { \
+ LL_FOREACH2(head,out,next) { \
+ if ((cmp(out,elt))==0) break; \
+ } \
+} while (0)
+
+#define LL_REPLACE_ELEM2(head, el, add, next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ assert((head) != NULL); \
+ assert((el) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el)->next; \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ _tmp = (head); \
+ while (_tmp->next && (_tmp->next != (el))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (add); \
+ } \
+ } \
+} while (0)
+
+#define LL_REPLACE_ELEM(head, el, add) \
+ LL_REPLACE_ELEM2(head, el, add, next)
+
+#define LL_PREPEND_ELEM2(head, el, add, next) \
+do { \
+ if (el) { \
+ LDECLTYPE(head) _tmp; \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ _tmp = (head); \
+ while (_tmp->next && (_tmp->next != (el))) { \
+ _tmp = _tmp->next; \
+ } \
+ if (_tmp->next) { \
+ _tmp->next = (add); \
+ } \
+ } \
+ } else { \
+ LL_APPEND2(head, add, next); \
+ } \
+} while (0) \
+
+#define LL_PREPEND_ELEM(head, el, add) \
+ LL_PREPEND_ELEM2(head, el, add, next)
+
+#define LL_APPEND_ELEM2(head, el, add, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el)->next; \
+ (el)->next = (add); \
+ } else { \
+ LL_PREPEND2(head, add, next); \
+ } \
+} while (0) \
+
+#define LL_APPEND_ELEM(head, el, add) \
+ LL_APPEND_ELEM2(head, el, add, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef LL_CONCAT2
+#define LL_CONCAT2(head1,head2,next) \
+do { \
+ char *_tmp; \
+ if (head1) { \
+ _tmp = (char*)(head1); \
+ while ((head1)->next) { (head1) = (head1)->next; } \
+ (head1)->next = (head2); \
+ UTLIST_RS(head1); \
+ } else { \
+ (head1)=(head2); \
+ } \
+} while (0)
+
+#undef LL_APPEND2
+#define LL_APPEND2(head,add,next) \
+do { \
+ if (head) { \
+ (add)->next = head; /* use add->next as a temp variable */ \
+ while ((add)->next->next) { (add)->next = (add)->next->next; } \
+ (add)->next->next=(add); \
+ } else { \
+ (head)=(add); \
+ } \
+ (add)->next=NULL; \
+} while (0)
+
+#undef LL_INSERT_INORDER2
+#define LL_INSERT_INORDER2(head,add,cmp,next) \
+do { \
+ if ((head) == NULL || (cmp(head, add)) >= 0) { \
+ (add)->next = (head); \
+ (head) = (add); \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((head)->next != NULL && (cmp((head)->next, add)) < 0) { \
+ (head) = (head)->next; \
+ } \
+ (add)->next = (head)->next; \
+ (head)->next = (add); \
+ UTLIST_RS(head); \
+ } \
+} while (0)
+
+#undef LL_DELETE2
+#define LL_DELETE2(head,del,next) \
+do { \
+ if ((head) == (del)) { \
+ (head)=(head)->next; \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((head)->next && ((head)->next != (del))) { \
+ (head) = (head)->next; \
+ } \
+ if ((head)->next) { \
+ (head)->next = ((del)->next); \
+ } \
+ UTLIST_RS(head); \
+ } \
+} while (0)
+
+#undef LL_REPLACE_ELEM2
+#define LL_REPLACE_ELEM2(head, el, add, next) \
+do { \
+ assert((head) != NULL); \
+ assert((el) != NULL); \
+ assert((add) != NULL); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ (add)->next = head; \
+ while ((add)->next->next && ((add)->next->next != (el))) { \
+ (add)->next = (add)->next->next; \
+ } \
+ if ((add)->next->next) { \
+ (add)->next->next = (add); \
+ } \
+ } \
+ (add)->next = (el)->next; \
+} while (0)
+
+#undef LL_PREPEND_ELEM2
+#define LL_PREPEND_ELEM2(head, el, add, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ (add)->next = (head); \
+ while ((add)->next->next && ((add)->next->next != (el))) { \
+ (add)->next = (add)->next->next; \
+ } \
+ if ((add)->next->next) { \
+ (add)->next->next = (add); \
+ } \
+ } \
+ (add)->next = (el); \
+ } else { \
+ LL_APPEND2(head, add, next); \
+ } \
+} while (0) \
+
+#endif /* NO_DECLTYPE */
+
+/******************************************************************************
+ * doubly linked list macros (non-circular) *
+ *****************************************************************************/
+#define DL_PREPEND(head,add) \
+ DL_PREPEND2(head,add,prev,next)
+
+#define DL_PREPEND2(head,add,prev,next) \
+do { \
+ (add)->next = (head); \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (head)->prev = (add); \
+ } else { \
+ (add)->prev = (add); \
+ } \
+ (head) = (add); \
+} while (0)
+
+#define DL_APPEND(head,add) \
+ DL_APPEND2(head,add,prev,next)
+
+#define DL_APPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (head)->prev->next = (add); \
+ (head)->prev = (add); \
+ (add)->next = NULL; \
+ } else { \
+ (head)=(add); \
+ (head)->prev = (head); \
+ (head)->next = NULL; \
+ } \
+} while (0)
+
+#define DL_INSERT_INORDER(head,add,cmp) \
+ DL_INSERT_INORDER2(head,add,cmp,prev,next)
+
+#define DL_INSERT_INORDER2(head,add,cmp,prev,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if (head) { \
+ DL_LOWER_BOUND2(head, _tmp, add, cmp, next); \
+ DL_APPEND_ELEM2(head, _tmp, add, prev, next); \
+ } else { \
+ (head) = (add); \
+ (head)->prev = (head); \
+ (head)->next = NULL; \
+ } \
+} while (0)
+
+#define DL_LOWER_BOUND(head,elt,like,cmp) \
+ DL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define DL_LOWER_BOUND2(head,elt,like,cmp,next) \
+do { \
+ if ((head) == NULL || (cmp(head, like)) >= 0) { \
+ (elt) = NULL; \
+ } else { \
+ for ((elt) = (head); (elt)->next != NULL; (elt) = (elt)->next) { \
+ if ((cmp((elt)->next, like)) >= 0) { \
+ break; \
+ } \
+ } \
+ } \
+} while (0)
+
+#define DL_CONCAT(head1,head2) \
+ DL_CONCAT2(head1,head2,prev,next)
+
+#define DL_CONCAT2(head1,head2,prev,next) \
+do { \
+ LDECLTYPE(head1) _tmp; \
+ if (head2) { \
+ if (head1) { \
+ UTLIST_CASTASGN(_tmp, (head2)->prev); \
+ (head2)->prev = (head1)->prev; \
+ (head1)->prev->next = (head2); \
+ UTLIST_CASTASGN((head1)->prev, _tmp); \
+ } else { \
+ (head1)=(head2); \
+ } \
+ } \
+} while (0)
+
+#define DL_DELETE(head,del) \
+ DL_DELETE2(head,del,prev,next)
+
+#define DL_DELETE2(head,del,prev,next) \
+do { \
+ assert((head) != NULL); \
+ assert((del)->prev != NULL); \
+ if ((del)->prev == (del)) { \
+ (head)=NULL; \
+ } else if ((del)==(head)) { \
+ (del)->next->prev = (del)->prev; \
+ (head) = (del)->next; \
+ } else { \
+ (del)->prev->next = (del)->next; \
+ if ((del)->next) { \
+ (del)->next->prev = (del)->prev; \
+ } else { \
+ (head)->prev = (del)->prev; \
+ } \
+ } \
+} while (0)
+
+#define DL_COUNT(head,el,counter) \
+ DL_COUNT2(head,el,counter,next) \
+
+#define DL_COUNT2(head,el,counter,next) \
+do { \
+ (counter) = 0; \
+ DL_FOREACH2(head,el,next) { ++(counter); } \
+} while (0)
+
+#define DL_FOREACH(head,el) \
+ DL_FOREACH2(head,el,next)
+
+#define DL_FOREACH2(head,el,next) \
+ for ((el) = (head); el; (el) = (el)->next)
+
+/* this version is safe for deleting the elements during iteration */
+#define DL_FOREACH_SAFE(head,el,tmp) \
+ DL_FOREACH_SAFE2(head,el,tmp,next)
+
+#define DL_FOREACH_SAFE2(head,el,tmp,next) \
+ for ((el) = (head); (el) && ((tmp) = (el)->next, 1); (el) = (tmp))
+
+/* these are identical to their singly-linked list counterparts */
+#define DL_SEARCH_SCALAR LL_SEARCH_SCALAR
+#define DL_SEARCH LL_SEARCH
+#define DL_SEARCH_SCALAR2 LL_SEARCH_SCALAR2
+#define DL_SEARCH2 LL_SEARCH2
+
+#define DL_REPLACE_ELEM2(head, el, add, prev, next) \
+do { \
+ assert((head) != NULL); \
+ assert((el) != NULL); \
+ assert((add) != NULL); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ (add)->next = (el)->next; \
+ if ((el)->next == NULL) { \
+ (add)->prev = (add); \
+ } else { \
+ (add)->prev = (el)->prev; \
+ (add)->next->prev = (add); \
+ } \
+ } else { \
+ (add)->next = (el)->next; \
+ (add)->prev = (el)->prev; \
+ (add)->prev->next = (add); \
+ if ((el)->next == NULL) { \
+ (head)->prev = (add); \
+ } else { \
+ (add)->next->prev = (add); \
+ } \
+ } \
+} while (0)
+
+#define DL_REPLACE_ELEM(head, el, add) \
+ DL_REPLACE_ELEM2(head, el, add, prev, next)
+
+#define DL_PREPEND_ELEM2(head, el, add, prev, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el); \
+ (add)->prev = (el)->prev; \
+ (el)->prev = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } else { \
+ (add)->prev->next = (add); \
+ } \
+ } else { \
+ DL_APPEND2(head, add, prev, next); \
+ } \
+} while (0) \
+
+#define DL_PREPEND_ELEM(head, el, add) \
+ DL_PREPEND_ELEM2(head, el, add, prev, next)
+
+#define DL_APPEND_ELEM2(head, el, add, prev, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el)->next; \
+ (add)->prev = (el); \
+ (el)->next = (add); \
+ if ((add)->next) { \
+ (add)->next->prev = (add); \
+ } else { \
+ (head)->prev = (add); \
+ } \
+ } else { \
+ DL_PREPEND2(head, add, prev, next); \
+ } \
+} while (0) \
+
+#define DL_APPEND_ELEM(head, el, add) \
+ DL_APPEND_ELEM2(head, el, add, prev, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef DL_INSERT_INORDER2
+#define DL_INSERT_INORDER2(head,add,cmp,prev,next) \
+do { \
+ if ((head) == NULL) { \
+ (add)->prev = (add); \
+ (add)->next = NULL; \
+ (head) = (add); \
+ } else if ((cmp(head, add)) >= 0) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (head)->prev = (add); \
+ (head) = (add); \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((head)->next && (cmp((head)->next, add)) < 0) { \
+ (head) = (head)->next; \
+ } \
+ (add)->prev = (head); \
+ (add)->next = (head)->next; \
+ (head)->next = (add); \
+ UTLIST_RS(head); \
+ if ((add)->next) { \
+ (add)->next->prev = (add); \
+ } else { \
+ (head)->prev = (add); \
+ } \
+ } \
+} while (0)
+#endif /* NO_DECLTYPE */
+
+/******************************************************************************
+ * circular doubly linked list macros *
+ *****************************************************************************/
+#define CDL_APPEND(head,add) \
+ CDL_APPEND2(head,add,prev,next)
+
+#define CDL_APPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (head)->prev = (add); \
+ (add)->prev->next = (add); \
+ } else { \
+ (add)->prev = (add); \
+ (add)->next = (add); \
+ (head) = (add); \
+ } \
+} while (0)
+
+#define CDL_PREPEND(head,add) \
+ CDL_PREPEND2(head,add,prev,next)
+
+#define CDL_PREPEND2(head,add,prev,next) \
+do { \
+ if (head) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (head)->prev = (add); \
+ (add)->prev->next = (add); \
+ } else { \
+ (add)->prev = (add); \
+ (add)->next = (add); \
+ } \
+ (head) = (add); \
+} while (0)
+
+#define CDL_INSERT_INORDER(head,add,cmp) \
+ CDL_INSERT_INORDER2(head,add,cmp,prev,next)
+
+#define CDL_INSERT_INORDER2(head,add,cmp,prev,next) \
+do { \
+ LDECLTYPE(head) _tmp; \
+ if (head) { \
+ CDL_LOWER_BOUND2(head, _tmp, add, cmp, next); \
+ CDL_APPEND_ELEM2(head, _tmp, add, prev, next); \
+ } else { \
+ (head) = (add); \
+ (head)->next = (head); \
+ (head)->prev = (head); \
+ } \
+} while (0)
+
+#define CDL_LOWER_BOUND(head,elt,like,cmp) \
+ CDL_LOWER_BOUND2(head,elt,like,cmp,next)
+
+#define CDL_LOWER_BOUND2(head,elt,like,cmp,next) \
+do { \
+ if ((head) == NULL || (cmp(head, like)) >= 0) { \
+ (elt) = NULL; \
+ } else { \
+ for ((elt) = (head); (elt)->next != (head); (elt) = (elt)->next) { \
+ if ((cmp((elt)->next, like)) >= 0) { \
+ break; \
+ } \
+ } \
+ } \
+} while (0)
+
+#define CDL_DELETE(head,del) \
+ CDL_DELETE2(head,del,prev,next)
+
+#define CDL_DELETE2(head,del,prev,next) \
+do { \
+ if (((head)==(del)) && ((head)->next == (head))) { \
+ (head) = NULL; \
+ } else { \
+ (del)->next->prev = (del)->prev; \
+ (del)->prev->next = (del)->next; \
+ if ((del) == (head)) (head)=(del)->next; \
+ } \
+} while (0)
+
+#define CDL_COUNT(head,el,counter) \
+ CDL_COUNT2(head,el,counter,next) \
+
+#define CDL_COUNT2(head, el, counter,next) \
+do { \
+ (counter) = 0; \
+ CDL_FOREACH2(head,el,next) { ++(counter); } \
+} while (0)
+
+#define CDL_FOREACH(head,el) \
+ CDL_FOREACH2(head,el,next)
+
+#define CDL_FOREACH2(head,el,next) \
+ for ((el)=(head);el;(el)=(((el)->next==(head)) ? NULL : (el)->next))
+
+#define CDL_FOREACH_SAFE(head,el,tmp1,tmp2) \
+ CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next)
+
+#define CDL_FOREACH_SAFE2(head,el,tmp1,tmp2,prev,next) \
+ for ((el) = (head), (tmp1) = (head) ? (head)->prev : NULL; \
+ (el) && ((tmp2) = (el)->next, 1); \
+ (el) = ((el) == (tmp1) ? NULL : (tmp2)))
+
+#define CDL_SEARCH_SCALAR(head,out,field,val) \
+ CDL_SEARCH_SCALAR2(head,out,field,val,next)
+
+#define CDL_SEARCH_SCALAR2(head,out,field,val,next) \
+do { \
+ CDL_FOREACH2(head,out,next) { \
+ if ((out)->field == (val)) break; \
+ } \
+} while (0)
+
+#define CDL_SEARCH(head,out,elt,cmp) \
+ CDL_SEARCH2(head,out,elt,cmp,next)
+
+#define CDL_SEARCH2(head,out,elt,cmp,next) \
+do { \
+ CDL_FOREACH2(head,out,next) { \
+ if ((cmp(out,elt))==0) break; \
+ } \
+} while (0)
+
+#define CDL_REPLACE_ELEM2(head, el, add, prev, next) \
+do { \
+ assert((head) != NULL); \
+ assert((el) != NULL); \
+ assert((add) != NULL); \
+ if ((el)->next == (el)) { \
+ (add)->next = (add); \
+ (add)->prev = (add); \
+ (head) = (add); \
+ } else { \
+ (add)->next = (el)->next; \
+ (add)->prev = (el)->prev; \
+ (add)->next->prev = (add); \
+ (add)->prev->next = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } \
+ } \
+} while (0)
+
+#define CDL_REPLACE_ELEM(head, el, add) \
+ CDL_REPLACE_ELEM2(head, el, add, prev, next)
+
+#define CDL_PREPEND_ELEM2(head, el, add, prev, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el); \
+ (add)->prev = (el)->prev; \
+ (el)->prev = (add); \
+ (add)->prev->next = (add); \
+ if ((head) == (el)) { \
+ (head) = (add); \
+ } \
+ } else { \
+ CDL_APPEND2(head, add, prev, next); \
+ } \
+} while (0)
+
+#define CDL_PREPEND_ELEM(head, el, add) \
+ CDL_PREPEND_ELEM2(head, el, add, prev, next)
+
+#define CDL_APPEND_ELEM2(head, el, add, prev, next) \
+do { \
+ if (el) { \
+ assert((head) != NULL); \
+ assert((add) != NULL); \
+ (add)->next = (el)->next; \
+ (add)->prev = (el); \
+ (el)->next = (add); \
+ (add)->next->prev = (add); \
+ } else { \
+ CDL_PREPEND2(head, add, prev, next); \
+ } \
+} while (0)
+
+#define CDL_APPEND_ELEM(head, el, add) \
+ CDL_APPEND_ELEM2(head, el, add, prev, next)
+
+#ifdef NO_DECLTYPE
+/* Here are VS2008 / NO_DECLTYPE replacements for a few functions */
+
+#undef CDL_INSERT_INORDER2
+#define CDL_INSERT_INORDER2(head,add,cmp,prev,next) \
+do { \
+ if ((head) == NULL) { \
+ (add)->prev = (add); \
+ (add)->next = (add); \
+ (head) = (add); \
+ } else if ((cmp(head, add)) >= 0) { \
+ (add)->prev = (head)->prev; \
+ (add)->next = (head); \
+ (add)->prev->next = (add); \
+ (head)->prev = (add); \
+ (head) = (add); \
+ } else { \
+ char *_tmp = (char*)(head); \
+ while ((char*)(head)->next != _tmp && (cmp((head)->next, add)) < 0) { \
+ (head) = (head)->next; \
+ } \
+ (add)->prev = (head); \
+ (add)->next = (head)->next; \
+ (add)->next->prev = (add); \
+ (head)->next = (add); \
+ UTLIST_RS(head); \
+ } \
+} while (0)
+#endif /* NO_DECLTYPE */
+
+#endif /* UTLIST_H */
diff --git a/deps/uthash/utringbuffer.h b/deps/uthash/utringbuffer.h
new file mode 100644
index 0000000..6034117
--- /dev/null
+++ b/deps/uthash/utringbuffer.h
@@ -0,0 +1,108 @@
+/*
+Copyright (c) 2015-2022, Troy D. Hanson https://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* a ring-buffer implementation using macros
+ */
+#ifndef UTRINGBUFFER_H
+#define UTRINGBUFFER_H
+
+#define UTRINGBUFFER_VERSION 2.3.0
+
+#include <stdlib.h>
+#include <string.h>
+#include "utarray.h" // for "UT_icd"
+
+typedef struct {
+ unsigned i; /* index of next available slot; wraps at n */
+ unsigned n; /* capacity */
+ unsigned char f; /* full */
+ UT_icd icd; /* initializer, copy and destructor functions */
+ char *d; /* n slots of size icd->sz */
+} UT_ringbuffer;
+
+#define utringbuffer_init(a, _n, _icd) do { \
+ memset(a, 0, sizeof(UT_ringbuffer)); \
+ (a)->icd = *(_icd); \
+ (a)->n = (_n); \
+ if ((a)->n) { (a)->d = (char*)malloc((a)->n * (_icd)->sz); } \
+} while(0)
+
+#define utringbuffer_clear(a) do { \
+ if ((a)->icd.dtor) { \
+ if ((a)->f) { \
+ unsigned _ut_i; \
+ for (_ut_i = 0; _ut_i < (a)->n; ++_ut_i) { \
+ (a)->icd.dtor(utringbuffer_eltptr(a, _ut_i)); \
+ } \
+ } else { \
+ unsigned _ut_i; \
+ for (_ut_i = 0; _ut_i < (a)->i; ++_ut_i) { \
+ (a)->icd.dtor(utringbuffer_eltptr(a, _ut_i)); \
+ } \
+ } \
+ } \
+ (a)->i = 0; \
+ (a)->f = 0; \
+} while(0)
+
+#define utringbuffer_done(a) do { \
+ utringbuffer_clear(a); \
+ free((a)->d); (a)->d = NULL; \
+ (a)->n = 0; \
+} while(0)
+
+#define utringbuffer_new(a,n,_icd) do { \
+ a = (UT_ringbuffer*)malloc(sizeof(UT_ringbuffer)); \
+ utringbuffer_init(a, n, _icd); \
+} while(0)
+
+#define utringbuffer_free(a) do { \
+ utringbuffer_done(a); \
+ free(a); \
+} while(0)
+
+#define utringbuffer_push_back(a,p) do { \
+ if ((a)->icd.dtor && (a)->f) { (a)->icd.dtor(_utringbuffer_internalptr(a,(a)->i)); } \
+ if ((a)->icd.copy) { (a)->icd.copy( _utringbuffer_internalptr(a,(a)->i), p); } \
+ else { memcpy(_utringbuffer_internalptr(a,(a)->i), p, (a)->icd.sz); }; \
+ if (++(a)->i == (a)->n) { (a)->i = 0; (a)->f = 1; } \
+} while(0)
+
+#define utringbuffer_len(a) ((a)->f ? (a)->n : (a)->i)
+#define utringbuffer_empty(a) ((a)->i == 0 && !(a)->f)
+#define utringbuffer_full(a) ((a)->f != 0)
+
+#define _utringbuffer_real_idx(a,j) ((a)->f ? ((j) + (a)->i) % (a)->n : (j))
+#define _utringbuffer_internalptr(a,j) ((void*)((a)->d + ((a)->icd.sz * (j))))
+#define utringbuffer_eltptr(a,j) ((0 <= (j) && (j) < utringbuffer_len(a)) ? _utringbuffer_internalptr(a,_utringbuffer_real_idx(a,j)) : NULL)
+
+#define _utringbuffer_fake_idx(a,j) ((a)->f ? ((j) + (a)->n - (a)->i) % (a)->n : (j))
+#define _utringbuffer_internalidx(a,e) (((char*)(e) >= (a)->d) ? (((char*)(e) - (a)->d)/(a)->icd.sz) : -1)
+#define utringbuffer_eltidx(a,e) _utringbuffer_fake_idx(a, _utringbuffer_internalidx(a,e))
+
+#define utringbuffer_front(a) utringbuffer_eltptr(a,0)
+#define utringbuffer_next(a,e) ((e)==NULL ? utringbuffer_front(a) : utringbuffer_eltptr(a, utringbuffer_eltidx(a,e)+1))
+#define utringbuffer_prev(a,e) ((e)==NULL ? utringbuffer_back(a) : utringbuffer_eltptr(a, utringbuffer_eltidx(a,e)-1))
+#define utringbuffer_back(a) (utringbuffer_empty(a) ? NULL : utringbuffer_eltptr(a, utringbuffer_len(a) - 1))
+
+#endif /* UTRINGBUFFER_H */
diff --git a/deps/uthash/utstack.h b/deps/uthash/utstack.h
new file mode 100644
index 0000000..94b8c51
--- /dev/null
+++ b/deps/uthash/utstack.h
@@ -0,0 +1,88 @@
+/*
+Copyright (c) 2018-2022, Troy D. Hanson https://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTSTACK_H
+#define UTSTACK_H
+
+#define UTSTACK_VERSION 2.3.0
+
+/*
+ * This file contains macros to manipulate a singly-linked list as a stack.
+ *
+ * To use utstack, your structure must have a "next" pointer.
+ *
+ * ----------------.EXAMPLE -------------------------
+ * struct item {
+ * int id;
+ * struct item *next;
+ * };
+ *
+ * struct item *stack = NULL;
+ *
+ * int main() {
+ * int count;
+ * struct item *tmp;
+ * struct item *item = malloc(sizeof *item);
+ * item->id = 42;
+ * STACK_COUNT(stack, tmp, count); assert(count == 0);
+ * STACK_PUSH(stack, item);
+ * STACK_COUNT(stack, tmp, count); assert(count == 1);
+ * STACK_POP(stack, item);
+ * free(item);
+ * STACK_COUNT(stack, tmp, count); assert(count == 0);
+ * }
+ * --------------------------------------------------
+ */
+
+#define STACK_TOP(head) (head)
+
+#define STACK_EMPTY(head) (!(head))
+
+#define STACK_PUSH(head,add) \
+ STACK_PUSH2(head,add,next)
+
+#define STACK_PUSH2(head,add,next) \
+do { \
+ (add)->next = (head); \
+ (head) = (add); \
+} while (0)
+
+#define STACK_POP(head,result) \
+ STACK_POP2(head,result,next)
+
+#define STACK_POP2(head,result,next) \
+do { \
+ (result) = (head); \
+ (head) = (head)->next; \
+} while (0)
+
+#define STACK_COUNT(head,el,counter) \
+ STACK_COUNT2(head,el,counter,next) \
+
+#define STACK_COUNT2(head,el,counter,next) \
+do { \
+ (counter) = 0; \
+ for ((el) = (head); el; (el) = (el)->next) { ++(counter); } \
+} while (0)
+
+#endif /* UTSTACK_H */
diff --git a/deps/uthash/utstring.h b/deps/uthash/utstring.h
new file mode 100644
index 0000000..f0270fb
--- /dev/null
+++ b/deps/uthash/utstring.h
@@ -0,0 +1,407 @@
+/*
+Copyright (c) 2008-2022, Troy D. Hanson https://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* a dynamic string implementation using macros
+ */
+#ifndef UTSTRING_H
+#define UTSTRING_H
+
+#define UTSTRING_VERSION 2.3.0
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#ifdef __GNUC__
+#define UTSTRING_UNUSED __attribute__((__unused__))
+#else
+#define UTSTRING_UNUSED
+#endif
+
+#ifdef oom
+#error "The name of macro 'oom' has been changed to 'utstring_oom'. Please update your code."
+#define utstring_oom() oom()
+#endif
+
+#ifndef utstring_oom
+#define utstring_oom() exit(-1)
+#endif
+
+typedef struct {
+ char *d; /* pointer to allocated buffer */
+ size_t n; /* allocated capacity */
+ size_t i; /* index of first unused byte */
+} UT_string;
+
+#define utstring_reserve(s,amt) \
+do { \
+ if (((s)->n - (s)->i) < (size_t)(amt)) { \
+ char *utstring_tmp = (char*)realloc( \
+ (s)->d, (s)->n + (amt)); \
+ if (!utstring_tmp) { \
+ utstring_oom(); \
+ } \
+ (s)->d = utstring_tmp; \
+ (s)->n += (amt); \
+ } \
+} while(0)
+
+#define utstring_init(s) \
+do { \
+ (s)->n = 0; (s)->i = 0; (s)->d = NULL; \
+ utstring_reserve(s,100); \
+ (s)->d[0] = '\0'; \
+} while(0)
+
+#define utstring_done(s) \
+do { \
+ if ((s)->d != NULL) free((s)->d); \
+ (s)->n = 0; \
+} while(0)
+
+#define utstring_free(s) \
+do { \
+ utstring_done(s); \
+ free(s); \
+} while(0)
+
+#define utstring_new(s) \
+do { \
+ (s) = (UT_string*)malloc(sizeof(UT_string)); \
+ if (!(s)) { \
+ utstring_oom(); \
+ } \
+ utstring_init(s); \
+} while(0)
+
+#define utstring_renew(s) \
+do { \
+ if (s) { \
+ utstring_clear(s); \
+ } else { \
+ utstring_new(s); \
+ } \
+} while(0)
+
+#define utstring_clear(s) \
+do { \
+ (s)->i = 0; \
+ (s)->d[0] = '\0'; \
+} while(0)
+
+#define utstring_bincpy(s,b,l) \
+do { \
+ utstring_reserve((s),(l)+1); \
+ if (l) memcpy(&(s)->d[(s)->i], b, l); \
+ (s)->i += (l); \
+ (s)->d[(s)->i]='\0'; \
+} while(0)
+
+#define utstring_concat(dst,src) \
+do { \
+ utstring_reserve((dst),((src)->i)+1); \
+ if ((src)->i) memcpy(&(dst)->d[(dst)->i], (src)->d, (src)->i); \
+ (dst)->i += (src)->i; \
+ (dst)->d[(dst)->i]='\0'; \
+} while(0)
+
+#define utstring_len(s) ((s)->i)
+
+#define utstring_body(s) ((s)->d)
+
+UTSTRING_UNUSED static void utstring_printf_va(UT_string *s, const char *fmt, va_list ap) {
+ int n;
+ va_list cp;
+ for (;;) {
+#ifdef _WIN32
+ cp = ap;
+#else
+ va_copy(cp, ap);
+#endif
+ n = vsnprintf (&s->d[s->i], s->n-s->i, fmt, cp);
+ va_end(cp);
+
+ if ((n > -1) && ((size_t) n < (s->n-s->i))) {
+ s->i += n;
+ return;
+ }
+
+ /* Else try again with more space. */
+ if (n > -1) utstring_reserve(s,n+1); /* exact */
+ else utstring_reserve(s,(s->n)*2); /* 2x */
+ }
+}
+#ifdef __GNUC__
+/* support printf format checking (2=the format string, 3=start of varargs) */
+static void utstring_printf(UT_string *s, const char *fmt, ...)
+ __attribute__ (( format( printf, 2, 3) ));
+#endif
+UTSTRING_UNUSED static void utstring_printf(UT_string *s, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap,fmt);
+ utstring_printf_va(s,fmt,ap);
+ va_end(ap);
+}
+
+/*******************************************************************************
+ * begin substring search functions *
+ ******************************************************************************/
+/* Build KMP table from left to right. */
+UTSTRING_UNUSED static void _utstring_BuildTable(
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+
+ i = 0;
+ j = i - 1;
+ P_KMP_Table[i] = j;
+ while (i < (long) P_NeedleLen)
+ {
+ while ( (j > -1) && (P_Needle[i] != P_Needle[j]) )
+ {
+ j = P_KMP_Table[j];
+ }
+ i++;
+ j++;
+ if (i < (long) P_NeedleLen)
+ {
+ if (P_Needle[i] == P_Needle[j])
+ {
+ P_KMP_Table[i] = P_KMP_Table[j];
+ }
+ else
+ {
+ P_KMP_Table[i] = j;
+ }
+ }
+ else
+ {
+ P_KMP_Table[i] = j;
+ }
+ }
+
+ return;
+}
+
+
+/* Build KMP table from right to left. */
+UTSTRING_UNUSED static void _utstring_BuildTableR(
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+
+ i = P_NeedleLen - 1;
+ j = i + 1;
+ P_KMP_Table[i + 1] = j;
+ while (i >= 0)
+ {
+ while ( (j < (long) P_NeedleLen) && (P_Needle[i] != P_Needle[j]) )
+ {
+ j = P_KMP_Table[j + 1];
+ }
+ i--;
+ j--;
+ if (i >= 0)
+ {
+ if (P_Needle[i] == P_Needle[j])
+ {
+ P_KMP_Table[i + 1] = P_KMP_Table[j + 1];
+ }
+ else
+ {
+ P_KMP_Table[i + 1] = j;
+ }
+ }
+ else
+ {
+ P_KMP_Table[i + 1] = j;
+ }
+ }
+
+ return;
+}
+
+
+/* Search data from left to right. ( Multiple search mode. ) */
+UTSTRING_UNUSED static long _utstring_find(
+ const char *P_Haystack,
+ size_t P_HaystackLen,
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+ long V_FindPosition = -1;
+
+ /* Search from left to right. */
+ i = j = 0;
+ while ( (j < (int)P_HaystackLen) && (((P_HaystackLen - j) + i) >= P_NeedleLen) )
+ {
+ while ( (i > -1) && (P_Needle[i] != P_Haystack[j]) )
+ {
+ i = P_KMP_Table[i];
+ }
+ i++;
+ j++;
+ if (i >= (int)P_NeedleLen)
+ {
+ /* Found. */
+ V_FindPosition = j - i;
+ break;
+ }
+ }
+
+ return V_FindPosition;
+}
+
+
+/* Search data from right to left. ( Multiple search mode. ) */
+UTSTRING_UNUSED static long _utstring_findR(
+ const char *P_Haystack,
+ size_t P_HaystackLen,
+ const char *P_Needle,
+ size_t P_NeedleLen,
+ long *P_KMP_Table)
+{
+ long i, j;
+ long V_FindPosition = -1;
+
+ /* Search from right to left. */
+ j = (P_HaystackLen - 1);
+ i = (P_NeedleLen - 1);
+ while ( (j >= 0) && (j >= i) )
+ {
+ while ( (i < (int)P_NeedleLen) && (P_Needle[i] != P_Haystack[j]) )
+ {
+ i = P_KMP_Table[i + 1];
+ }
+ i--;
+ j--;
+ if (i < 0)
+ {
+ /* Found. */
+ V_FindPosition = j + 1;
+ break;
+ }
+ }
+
+ return V_FindPosition;
+}
+
+
+/* Search data from left to right. ( One time search mode. ) */
+UTSTRING_UNUSED static long utstring_find(
+ UT_string *s,
+ long P_StartPosition, /* Start from 0. -1 means last position. */
+ const char *P_Needle,
+ size_t P_NeedleLen)
+{
+ long V_StartPosition;
+ long V_HaystackLen;
+ long *V_KMP_Table;
+ long V_FindPosition = -1;
+
+ if (P_StartPosition < 0)
+ {
+ V_StartPosition = s->i + P_StartPosition;
+ }
+ else
+ {
+ V_StartPosition = P_StartPosition;
+ }
+ V_HaystackLen = s->i - V_StartPosition;
+ if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
+ {
+ V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
+ if (V_KMP_Table != NULL)
+ {
+ _utstring_BuildTable(P_Needle, P_NeedleLen, V_KMP_Table);
+
+ V_FindPosition = _utstring_find(s->d + V_StartPosition,
+ V_HaystackLen,
+ P_Needle,
+ P_NeedleLen,
+ V_KMP_Table);
+ if (V_FindPosition >= 0)
+ {
+ V_FindPosition += V_StartPosition;
+ }
+
+ free(V_KMP_Table);
+ }
+ }
+
+ return V_FindPosition;
+}
+
+
+/* Search data from right to left. ( One time search mode. ) */
+UTSTRING_UNUSED static long utstring_findR(
+ UT_string *s,
+ long P_StartPosition, /* Start from 0. -1 means last position. */
+ const char *P_Needle,
+ size_t P_NeedleLen)
+{
+ long V_StartPosition;
+ long V_HaystackLen;
+ long *V_KMP_Table;
+ long V_FindPosition = -1;
+
+ if (P_StartPosition < 0)
+ {
+ V_StartPosition = s->i + P_StartPosition;
+ }
+ else
+ {
+ V_StartPosition = P_StartPosition;
+ }
+ V_HaystackLen = V_StartPosition + 1;
+ if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
+ {
+ V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
+ if (V_KMP_Table != NULL)
+ {
+ _utstring_BuildTableR(P_Needle, P_NeedleLen, V_KMP_Table);
+
+ V_FindPosition = _utstring_findR(s->d,
+ V_HaystackLen,
+ P_Needle,
+ P_NeedleLen,
+ V_KMP_Table);
+
+ free(V_KMP_Table);
+ }
+ }
+
+ return V_FindPosition;
+}
+/*******************************************************************************
+ * end substring search functions *
+ ******************************************************************************/
+
+#endif /* UTSTRING_H */
diff --git a/include/http_decoder.h b/include/http_decoder.h
new file mode 100644
index 0000000..fe7d1c2
--- /dev/null
+++ b/include/http_decoder.h
@@ -0,0 +1,118 @@
+/*
+**********************************************************************************************
+* File: http_decoder.h
+* Description:
+* Authors: Liu WenTan <[email protected]>
+* Date: 2024-01-10
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+#ifndef _HTTP_DECODER_H_
+#define _HTTP_DECODER_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stddef.h>
+
+enum http_message_type {
+ HTTP_MESSAGE_REQ_LINE,
+ HTTP_MESSAGE_REQ_HEADER,
+ HTTP_MESSAGE_REQ_BODY,
+ HTTP_MESSAGE_RES_LINE,
+ HTTP_MESSAGE_RES_HEADER,
+ HTTP_MESSAGE_RES_BODY,
+ HTTP_MESSAGE_MAX
+};
+
+//http string
+struct hstring {
+ char *str;
+ size_t str_len;
+};
+
+struct http_header {
+ struct hstring key;
+ struct hstring val;
+};
+
+struct http_request_line {
+ struct hstring method;
+ struct hstring uri;
+ struct hstring version;
+
+ int major_version;
+ int minor_version;
+};
+
+struct http_response_line {
+ struct hstring version;
+ struct hstring status;
+
+ int major_version;
+ int minor_version;
+ int status_code;
+};
+
+struct http_message;
+
+enum http_message_type http_message_type(struct http_message *msg);
+
+/**
+ * @retval succeed(0) failed(-1)
+*/
+int http_message_get_request_line(struct http_message *msg,
+ struct http_request_line *line);
+
+int http_message_get_response_line(struct http_message *msg,
+ struct http_response_line *line);
+
+/* same key may has multiple kv */
+int http_message_get_request_header(struct http_message *msg, struct hstring *key,
+ struct http_header *hdr_array, size_t array_size);
+
+int http_message_get_response_header(struct http_message *msg, struct hstring *key,
+ struct http_header *hdr_array, size_t array_size);
+
+/**
+ * @brief loop reading all headers
+ *
+ * @retval succeed(1) failed(<= 0)
+*/
+int http_message_request_header_next(struct http_message *msg,
+ struct http_header *header);
+
+int http_message_response_header_next(struct http_message *msg,
+ struct http_header *header);
+
+/**
+ * @retval succeed(0) failed(-1)
+*/
+int http_message_get_request_raw_body(struct http_message *msg,
+ struct hstring *body);
+
+int http_message_get_response_raw_body(struct http_message *msg,
+ struct hstring *body);
+
+/**
+ * @brief If the body hasn't been compressed, return raw body
+ *
+ * @retval succeed(0) failed(-1)
+*/
+int http_message_get_request_decompress_body(struct http_message *msg,
+ struct hstring *body);
+
+int http_message_get_response_decompress_body(struct http_message *msg,
+ struct hstring *body);
+
+int http_message_get_url(struct http_message *msg, struct hstring *url);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/readme.md b/readme.md
index ea786ff..9e9113d 100644
--- a/readme.md
+++ b/readme.md
@@ -1 +1,2 @@
-readme \ No newline at end of file
+# Http decoder
+### This is a HTTP protocol parsing library based on llhttp.
diff --git a/src/.gitkeep b/src/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/.gitkeep
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..2312d62
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,18 @@
+add_definitions(-fPIC)
+
+include_directories(/opt/MESA/include/)
+include_directories(${PROJECT_SOURCE_DIR}/deps/)
+
+aux_source_directory(${PROJECT_SOURCE_DIR}/deps/mempool DEPS_SRC)
+aux_source_directory(${PROJECT_SOURCE_DIR}/deps/toml DEPS_SRC)
+
+set(HTTP_SRC ${DEPS_SRC} http_decoder.c http_decoder_utils.c http_decoder_half.c
+ http_decoder_table.c http_decoder_string.c http_content_decompress.c
+ http_decoder_result_queue.c)
+
+add_library(http_decoder SHARED ${HTTP_SRC})
+set_target_properties(http_decoder PROPERTIES LINK_FLAGS "-Wl,--version-script=${PROJECT_SOURCE_DIR}/src/version.map")
+target_link_libraries(http_decoder z brotlidec llhttp-static fieldstat4)
+set_target_properties(http_decoder PROPERTIES PREFIX "")
+
+install(TARGETS http_decoder LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/stellar_plugin/${lib_name} COMPONENT LIBRARIES) \ No newline at end of file
diff --git a/src/http_content_decompress.c b/src/http_content_decompress.c
new file mode 100644
index 0000000..5fe08a8
--- /dev/null
+++ b/src/http_content_decompress.c
@@ -0,0 +1,268 @@
+/*
+**********************************************************************************************
+* File: http_content_decompress.c
+* Description:
+* Authors: LuWenPeng <[email protected]>
+* Date: 2022-10-31
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+
+#include <zlib.h>
+#include <string.h>
+#include <assert.h>
+#include <brotli/decode.h>
+
+#include "stellar/utils.h"
+#include "http_decoder_utils.h"
+#include "http_content_decompress.h"
+
+#define BUFFER_SIZE (16 * 1024)
+
+struct http_content_decompress {
+ enum http_content_encoding encoding;
+
+ z_stream *z_stream_ptr;
+ BrotliDecoderState *br_state;
+
+ char *buffer;
+ size_t buffer_size;
+};
+
+enum http_content_encoding
+http_content_encoding_str2int(const char *content_encoding)
+{
+ if (strcasestr(content_encoding, "gzip") != NULL) {
+ return HTTP_CONTENT_ENCODING_GZIP;
+ }
+
+ if (strcasestr(content_encoding, "deflate") != NULL) {
+ return HTTP_CONTENT_ENCODING_DEFLATE;
+ }
+
+ if (strcasestr(content_encoding, "br") != NULL) {
+ return HTTP_CONTENT_ENCODING_BR;
+ }
+
+ return HTTP_CONTENT_ENCODING_NONE;
+}
+
+const char *
+http_content_encoding_int2str(enum http_content_encoding content_encoding)
+{
+ if (content_encoding == HTTP_CONTENT_ENCODING_GZIP) {
+ return "gzip";
+ }
+
+ if (content_encoding == HTTP_CONTENT_ENCODING_DEFLATE) {
+ return "deflate";
+ }
+
+ if (content_encoding == HTTP_CONTENT_ENCODING_BR) {
+ return "br";
+ }
+
+ return "unknown";
+}
+
+struct http_content_decompress *
+http_content_decompress_create(enum http_content_encoding encoding)
+{
+ struct http_content_decompress *decompress =
+ CALLOC(struct http_content_decompress, 1);
+ assert(decompress);
+
+ decompress->encoding = encoding;
+ decompress->z_stream_ptr = NULL;
+ decompress->br_state = NULL;
+
+ decompress->buffer = CALLOC(char, BUFFER_SIZE);
+ assert(decompress->buffer);
+ decompress->buffer_size = BUFFER_SIZE;
+
+ if (encoding == HTTP_CONTENT_ENCODING_GZIP
+ || encoding == HTTP_CONTENT_ENCODING_DEFLATE) {
+ decompress->z_stream_ptr = CALLOC(z_stream, 1);
+ assert(decompress->z_stream_ptr);
+
+ decompress->z_stream_ptr->zalloc = NULL;
+ decompress->z_stream_ptr->zfree = NULL;
+ decompress->z_stream_ptr->opaque = NULL;
+ decompress->z_stream_ptr->avail_in = 0;
+ decompress->z_stream_ptr->next_in = Z_NULL;
+
+ if (encoding == HTTP_CONTENT_ENCODING_GZIP) {
+ if (inflateInit2(decompress->z_stream_ptr, MAX_WBITS + 16)
+ != Z_OK) {
+ goto error;
+ }
+ }
+
+ if (encoding == HTTP_CONTENT_ENCODING_DEFLATE) {
+ if (inflateInit2(decompress->z_stream_ptr, -MAX_WBITS) != Z_OK) {
+ goto error;
+ }
+ }
+ }
+
+ if (encoding == HTTP_CONTENT_ENCODING_BR) {
+ decompress->br_state = BrotliDecoderCreateInstance(NULL, NULL, NULL);
+ if (decompress->br_state == NULL) {
+ goto error;
+ }
+ }
+
+ return decompress;
+
+error:
+ http_content_decompress_destroy(decompress);
+ return NULL;
+}
+
+void http_content_decompress_destroy(struct http_content_decompress *decompress)
+{
+ if (NULL == decompress) {
+ return;
+ }
+
+ if (decompress->z_stream_ptr != NULL) {
+ inflateEnd(decompress->z_stream_ptr);
+ FREE(decompress->z_stream_ptr);
+ }
+
+ if (decompress->br_state) {
+ BrotliDecoderDestroyInstance(decompress->br_state);
+ decompress->br_state = NULL;
+ }
+
+ FREE(decompress->buffer);
+ FREE(decompress);
+}
+
+static int
+http_content_decompress_write_zlib(struct http_content_decompress *decompress,
+ const char *indata, size_t indata_len,
+ char **outdata, size_t *outdata_len)
+{
+ z_stream *z_stream_ptr = decompress->z_stream_ptr;
+ z_stream_ptr->avail_in = (unsigned int)indata_len;
+ z_stream_ptr->next_in = (unsigned char *)indata;
+ z_stream_ptr->avail_out = (unsigned int)decompress->buffer_size;
+ z_stream_ptr->next_out = (unsigned char *)decompress->buffer;
+
+ *outdata = NULL;
+ *outdata_len = 0;
+
+ int ret = 0;
+ do {
+ ret = inflate(z_stream_ptr, Z_NO_FLUSH);
+ if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT
+ || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
+ (void)inflateEnd(z_stream_ptr);
+ return -1;
+ }
+
+ size_t have = decompress->buffer_size - z_stream_ptr->avail_out;
+ if (have > 0) {
+ if (0 == z_stream_ptr->avail_out) {
+ decompress->buffer_size += have;
+ decompress->buffer = REALLOC(char, decompress->buffer,
+ decompress->buffer_size);
+ *outdata = decompress->buffer;
+ *outdata_len = *outdata_len + have;
+ http_decoder_log(DEBUG, "%s realloc outbuffer %zu bytes",
+ http_content_encoding_int2str(decompress->encoding),
+ decompress->buffer_size);
+ z_stream_ptr->avail_out = have;
+ z_stream_ptr->next_out = (unsigned char *)decompress->buffer +
+ (decompress->buffer_size - have);
+ } else {
+ *outdata = decompress->buffer;
+ *outdata_len = have;
+ }
+ }
+ } while (z_stream_ptr->avail_in != 0);
+
+ return 0;
+}
+
+static int
+http_content_decompress_write_br(struct http_content_decompress *decompress,
+ const char *indata, size_t indata_len,
+ char **outdata, size_t *outdata_len)
+{
+ size_t available_in = indata_len;
+ const unsigned char *next_in = (const unsigned char *)indata;
+ size_t available_out = decompress->buffer_size;
+ unsigned char *next_out = (unsigned char *)decompress->buffer;
+
+ *outdata = NULL;
+ *outdata_len = 0;
+
+ int ret;
+ for (;;) {
+ ret = BrotliDecoderDecompressStream(decompress->br_state, &available_in,
+ &next_in, &available_out, &next_out, 0);
+ size_t have = decompress->buffer_size - available_out;
+ if (have > 0) {
+ if (0 == available_out) {
+ decompress->buffer_size += have;
+ decompress->buffer = REALLOC(char, decompress->buffer,
+ decompress->buffer_size);
+ *outdata = decompress->buffer;
+ *outdata_len = *outdata_len + have;
+ available_out = have;
+ next_out = (unsigned char *)decompress->buffer +
+ (decompress->buffer_size - have);
+ } else {
+ *outdata = decompress->buffer;
+ *outdata_len = have;
+ }
+ }
+
+ if (ret == BROTLI_DECODER_RESULT_SUCCESS ||
+ ret == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
+ return 0;
+ }
+
+ if (ret == BROTLI_DECODER_RESULT_ERROR) {
+ BrotliDecoderErrorCode errcode =
+ BrotliDecoderGetErrorCode(decompress->br_state);
+ http_decoder_log(ERROR,
+ "BrotliDecoderDecompressStream() failed: errno = %d, %s",
+ errcode, BrotliDecoderErrorString(errcode));
+ return -1;
+ }
+
+ assert(ret == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
+ }
+}
+
+int http_content_decompress_write(struct http_content_decompress *decompress,
+ const char *indata, size_t indata_len,
+ char **outdata, size_t *outdata_len)
+{
+ assert(decompress);
+ assert(indata);
+ assert(indata_len > 0);
+ assert(outdata);
+ assert(outdata_len);
+
+ *outdata = NULL;
+ *outdata_len = 0;
+
+ if (decompress->encoding == HTTP_CONTENT_ENCODING_GZIP ||
+ decompress->encoding == HTTP_CONTENT_ENCODING_DEFLATE) {
+ return http_content_decompress_write_zlib(decompress, indata, indata_len,
+ outdata, outdata_len);
+ }
+
+ if (decompress->encoding == HTTP_CONTENT_ENCODING_BR) {
+ return http_content_decompress_write_br(decompress, indata, indata_len,
+ outdata, outdata_len);
+ }
+
+ assert(0);
+ return -1;
+} \ No newline at end of file
diff --git a/src/http_content_decompress.h b/src/http_content_decompress.h
new file mode 100644
index 0000000..3c2ba48
--- /dev/null
+++ b/src/http_content_decompress.h
@@ -0,0 +1,52 @@
+/*
+**********************************************************************************************
+* File: http_content_decompress.h
+* Description:
+* Authors: LuWenPeng <[email protected]>
+* Date: 2022-10-31
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+
+#ifndef _HTTP_CONTENT_DECOMPRESS_H_
+#define _HTTP_CONTENT_DECOMPRESS_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stddef.h>
+
+enum http_content_encoding {
+ HTTP_CONTENT_ENCODING_NONE = 0,
+ HTTP_CONTENT_ENCODING_GZIP = 1 << 1,
+ HTTP_CONTENT_ENCODING_DEFLATE = 1 << 2,
+ HTTP_CONTENT_ENCODING_BR = 1 << 3,
+};
+
+struct http_content_decompress;
+
+enum http_content_encoding
+http_content_encoding_str2int(const char *content_encoding);
+
+const char *
+http_content_encoding_int2str(enum http_content_encoding content_encoding);
+
+struct http_content_decompress *
+http_content_decompress_create(enum http_content_encoding encoding);
+
+void http_content_decompress_destroy(struct http_content_decompress *decompress);
+
+// return 0 : success
+// return -1 : error
+int http_content_decompress_write(struct http_content_decompress *decompress,
+ const char *indata, size_t indata_len,
+ char **outdata, size_t *outdata_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/src/http_decoder.c b/src/http_decoder.c
new file mode 100644
index 0000000..68f7c58
--- /dev/null
+++ b/src/http_decoder.c
@@ -0,0 +1,858 @@
+/*
+**********************************************************************************************
+* File: http_decoder.c
+* Description:
+* Authors: Liu WenTan <[email protected]>
+* Date: 2024-01-10
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "toml/toml.h"
+#include "stellar/utils.h"
+#include "stellar/session.h"
+#include "stellar/session_mq.h"
+#include "stellar/session_exdata.h"
+#include "http_decoder.h"
+#include "http_decoder_half.h"
+#include "http_decoder_table.h"
+#include "http_decoder_result_queue.h"
+#include "llhttp.h"
+#include "http_decoder_inc.h"
+#include "fieldstat/fieldstat_easy.h"
+
+#define HTTP_IDENTIFY_LEN 16
+#define HD_RESULT_QUEUE_LEN 16
+
+#define DEFAULT_STAT_OUTPUT_INTERVAL 1
+#define DEFAULT_STAT_INTERVAL_PKTS 1000
+#define DEFAULT_MEMPOOL_SIZE (32 * 1024)
+
+const char *g_hd_cfg_path = "./etc/http/http_decoder.toml";
+const char *http_decoder_topic = "HTTP_DECODER_MESSAGE";
+const char *fs_file_name = "http_decoder.fs";
+
+struct http_decoder_config {
+ int decompress_switch;
+ int stat_interval_pkts; //call fieldstat_incrby every stat_interval_pkts
+ int stat_output_interval;
+ size_t result_queue_len; // per session result queue length
+ size_t mempool_size; // per session mempool size
+};
+
+/**
+ * NOTE: http_message don't have the ownership of data
+ */
+struct http_message {
+ enum http_message_type type;
+ struct http_decoder_result_queue *ref_queue;
+ size_t queue_index;
+};
+
+struct http_decoder {
+ struct http_decoder_half *c2s_half;
+ struct http_decoder_half *s2c_half;
+};
+
+struct http_decoder_exdata {
+ struct http_decoder_result_queue *queue;
+ struct http_decoder *decoder;
+ nmx_pool_t *mempool;
+};
+
+struct http_decoder_stat {
+ long long incoming_bytes;
+ long long incoming_pkts;
+ long long incoming_trans;
+ long long err_pkts;
+ int counter;
+};
+
+struct http_decoder_context {
+ int plugin_id;
+ int topic_id;
+ int ex_data_idx;
+ int fs_incoming_bytes_id;
+ int fs_incoming_pkts_id;
+ int fs_incoming_trans_id;
+ int fs_err_pkts_id;
+ struct stellar *st;
+ struct fieldstat_easy *fse;
+ struct http_decoder_config hd_cfg;
+};
+
+__thread struct http_decoder_stat _th_stat;
+
+struct http_message *
+http_message_new(enum http_message_type type,
+ struct http_decoder_result_queue *queue,
+ int queue_index)
+{
+ struct http_message *msg = CALLOC(struct http_message, 1);
+
+ msg->type = type;
+ msg->ref_queue = queue;
+ msg->queue_index = queue_index;
+
+ return msg;
+}
+
+static void http_message_free(void *http_msg, void *cb_arg)
+{
+ if (NULL == http_msg) {
+ return;
+ }
+
+ FREE(http_msg);
+}
+
+static void http_event_handler(enum http_event event,
+ struct http_decoder_half_data **data,
+ struct http_event_context *ev_ctx)
+{
+ assert(ev_ctx);
+
+ size_t queue_idx = 0;
+ nmx_pool_t *mempool = ev_ctx->ref_mempool;
+ struct http_decoder_result_queue *queue = ev_ctx->ref_queue;
+ struct http_message *msg = NULL;
+ struct http_decoder_half_data *half_data = NULL;
+ int ret = 0;
+
+ switch (event) {
+ case HTTP_EVENT_REQ_INIT:
+ half_data = http_decoder_result_queue_peek_req(queue);
+ if (half_data != NULL) {
+ http_decoder_result_queue_inc_req_index(queue);
+ }
+
+ half_data = http_decoder_result_queue_peek_req(queue);
+ if (half_data != NULL) {
+ half_data = http_decoder_result_queue_pop_req(queue);
+ http_decoder_half_data_free(mempool, half_data);
+ half_data = NULL;
+ }
+
+ half_data = http_decoder_half_data_new(mempool);
+ ret = http_decoder_result_queue_push_req(queue, half_data);
+ if (ret < 0) {
+ fprintf(stderr, "http_decoder_result_queue_push req failed.");
+ http_decoder_half_data_free(mempool, half_data);
+ half_data = NULL;
+ }
+ *data = half_data;
+ break;
+ case HTTP_EVENT_REQ_LINE:
+ queue_idx = http_decoder_result_queue_req_index(queue);
+ msg = http_message_new(HTTP_MESSAGE_REQ_LINE, queue, queue_idx);
+ session_mq_publish_message(ev_ctx->ref_session, ev_ctx->topic_id, msg);
+ break;
+ case HTTP_EVENT_REQ_HDR_END:
+ {
+ int build_url_final = http_decoder_join_url_finally(ev_ctx, http_decoder_result_queue_peek_req(queue),mempool);
+ ret = http_decoder_half_data_has_parsed_header(*data);
+ if (0 == ret && 0 == build_url_final) {
+ break;
+ }
+ queue_idx = http_decoder_result_queue_req_index(queue);
+ msg = http_message_new(HTTP_MESSAGE_REQ_HEADER, queue, queue_idx);
+ session_mq_publish_message(ev_ctx->ref_session, ev_ctx->topic_id, msg);
+ }
+ break;
+ case HTTP_EVENT_REQ_BODY_BEGIN:
+ break;
+ case HTTP_EVENT_REQ_BODY_DATA:
+ queue_idx = http_decoder_result_queue_req_index(queue);
+ msg = http_message_new(HTTP_MESSAGE_REQ_BODY, queue, queue_idx);
+ session_mq_publish_message(ev_ctx->ref_session, ev_ctx->topic_id, msg);
+ break;
+ case HTTP_EVENT_REQ_BODY_END:
+ break;
+ case HTTP_EVENT_REQ_END:
+ http_decoder_result_queue_inc_req_index(queue);
+ half_data = http_decoder_result_queue_pop_req(queue);
+ if (half_data != NULL) {
+ http_decoder_half_data_free(mempool, half_data);
+ half_data = NULL;
+ }
+ break;
+ case HTTP_EVENT_RES_INIT:
+ half_data = http_decoder_result_queue_peek_res(queue);
+ if (half_data != NULL) {
+ http_decoder_result_queue_inc_res_index(queue);
+ }
+
+ half_data = http_decoder_result_queue_peek_res(queue);
+ if (half_data != NULL) {
+ half_data = http_decoder_result_queue_pop_res(queue);
+ http_decoder_half_data_free(mempool, half_data);
+ half_data = NULL;
+ }
+
+ half_data = http_decoder_half_data_new(mempool);
+ ret = http_decoder_result_queue_push_res(queue, half_data);
+ if (ret < 0) {
+ fprintf(stderr, "http_decoder_result_queue_push res failed.");
+ http_decoder_half_data_free(mempool, half_data);
+ half_data = NULL;
+ }
+ *data = half_data;
+ break;
+ case HTTP_EVENT_RES_LINE:
+ queue_idx = http_decoder_result_queue_res_index(queue);
+ msg = http_message_new(HTTP_MESSAGE_RES_LINE, queue, queue_idx);
+ session_mq_publish_message(ev_ctx->ref_session, ev_ctx->topic_id, msg);
+ break;
+ case HTTP_EVENT_RES_HDR:
+ break;
+ case HTTP_EVENT_RES_HDR_END:
+ ret = http_decoder_half_data_has_parsed_header(*data);
+ if (0 == ret) {
+ break;
+ }
+
+ queue_idx = http_decoder_result_queue_res_index(queue);
+ msg = http_message_new(HTTP_MESSAGE_RES_HEADER, queue, queue_idx);
+ session_mq_publish_message(ev_ctx->ref_session, ev_ctx->topic_id, msg);
+ break;
+ case HTTP_EVENT_RES_BODY_BEGIN:
+ break;
+ case HTTP_EVENT_RES_BODY_DATA:
+ queue_idx = http_decoder_result_queue_res_index(queue);
+ msg = http_message_new(HTTP_MESSAGE_RES_BODY, queue, queue_idx);
+ session_mq_publish_message(ev_ctx->ref_session, ev_ctx->topic_id, msg);
+ break;
+ case HTTP_EVENT_RES_BODY_END:
+ break;
+ case HTTP_EVENT_RES_END:
+ http_decoder_result_queue_inc_res_index(queue);
+ half_data = http_decoder_result_queue_pop_res(queue);
+ if (half_data != NULL) {
+ http_decoder_half_data_free(mempool, half_data);
+ half_data = NULL;
+ }
+ break;
+ default:
+ assert(0);
+ break;
+ }
+}
+
+static struct http_decoder *
+http_decoder_new(nmx_pool_t *mempool, http_event_cb *ev_cb,
+ int decompress_switch)
+{
+ struct http_decoder *decoder = MEMPOOL_CALLOC(mempool, struct http_decoder, 1);
+ assert(decoder);
+
+ decoder->c2s_half = http_decoder_half_new(mempool, ev_cb, HTTP_REQUEST,
+ decompress_switch);
+ decoder->s2c_half = http_decoder_half_new(mempool, ev_cb, HTTP_RESPONSE,
+ decompress_switch);
+
+ return decoder;
+}
+
+static void
+http_decoder_free(nmx_pool_t *mempool, struct http_decoder *decoder)
+{
+ if (NULL == decoder) {
+ return;
+ }
+
+ if (decoder->c2s_half != NULL) {
+ http_decoder_half_free(mempool, decoder->c2s_half);
+ decoder->c2s_half = NULL;
+ }
+
+ if (decoder->s2c_half != NULL) {
+ http_decoder_half_free(mempool, decoder->s2c_half);
+ decoder->s2c_half = NULL;
+ }
+
+ MEMPOOL_FREE(mempool, decoder);
+}
+
+static struct http_decoder_exdata *
+http_decoder_exdata_new(size_t mempool_size, size_t queue_size,
+ int decompress_switch)
+{
+ struct http_decoder_exdata *ex_data = CALLOC(struct http_decoder_exdata, 1);
+
+ ex_data->mempool = nmx_create_pool(mempool_size);
+ ex_data->decoder = http_decoder_new(ex_data->mempool, http_event_handler,
+ decompress_switch);
+ ex_data->queue = http_decoder_result_queue_new(ex_data->mempool, queue_size);
+
+ return ex_data;
+}
+
+static void http_decoder_exdata_free(struct http_decoder_exdata *ex_data)
+{
+ if (NULL == ex_data) {
+ return;
+ }
+
+ if (ex_data->decoder != NULL) {
+ http_decoder_free(ex_data->mempool, ex_data->decoder);
+ ex_data->decoder = NULL;
+ }
+
+ if (ex_data->queue != NULL) {
+ http_decoder_result_queue_free(ex_data->mempool, ex_data->queue);
+ ex_data->queue = NULL;
+ }
+
+ nmx_destroy_pool(ex_data->mempool);
+
+ FREE(ex_data);
+}
+
+static int http_protocol_identify(const char *data, size_t data_len)
+{
+ llhttp_t parser;
+ llhttp_settings_t settings;
+ enum llhttp_errno error;
+
+ llhttp_settings_init(&settings);
+ llhttp_init(&parser, HTTP_BOTH, &settings);
+
+ error = llhttp_execute(&parser, data, data_len);
+ if (error != HPE_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+http_decoder_stat_init(struct http_decoder_context *ctx, int thread_num)
+{
+ ctx->fse =
+ fieldstat_easy_new(thread_num, "http_decoder_statistics", NULL, 0);
+ if (NULL == ctx->fse) {
+ fprintf(stderr, "fieldstat_easy_new failed.");
+ return -1;
+ }
+
+ ctx->fs_incoming_bytes_id =
+ fieldstat_easy_register_counter(ctx->fse, "incoming_bytes");
+ if (ctx->fs_incoming_bytes_id < 0) {
+ fprintf(stderr, "fieldstat_easy_register_counter incoming_bytes failed.");
+ return -1;
+ }
+
+ ctx->fs_incoming_trans_id =
+ fieldstat_easy_register_counter(ctx->fse, "incoming_trans");
+ if (ctx->fs_incoming_trans_id < 0) {
+ fprintf(stderr, "fieldstat_easy_register_counter incoming_trans failed.");
+ return -1;
+ }
+
+ ctx->fs_incoming_pkts_id =
+ fieldstat_easy_register_counter(ctx->fse, "incoming_pkts");
+ if (ctx->fs_incoming_pkts_id < 0) {
+ fprintf(stderr, "fieldstat_easy_register_counter incoming_pkts failed.");
+ return -1;
+ }
+
+ ctx->fs_err_pkts_id = fieldstat_easy_register_counter(ctx->fse, "err_pkts");
+ if (ctx->fs_err_pkts_id < 0) {
+ fprintf(stderr, "fieldstat_easy_register_counter err_pkts failed.");
+ return -1;
+ }
+
+ int stat_output_interval = DEFAULT_STAT_OUTPUT_INTERVAL;
+ if (ctx->hd_cfg.stat_output_interval > 0) {
+ stat_output_interval = ctx->hd_cfg.stat_output_interval;
+ }
+
+ int ret = fieldstat_easy_enable_auto_output(ctx->fse, fs_file_name,
+ stat_output_interval);
+ if (ret < 0) {
+ fprintf(stderr, "fieldstat_easy_enable_auto_output failed.");
+ return -1;
+ }
+
+ sleep(1);
+
+ return 0;
+}
+
+static void
+http_decoder_stat_output(struct http_decoder_context *ctx, int thread_id)
+{
+ if (NULL == ctx || thread_id < 0) {
+ return;
+ }
+
+ int stat_interval_pkts = DEFAULT_STAT_INTERVAL_PKTS;
+ if (ctx->hd_cfg.stat_interval_pkts > 0) {
+ stat_interval_pkts = ctx->hd_cfg.stat_interval_pkts;
+ }
+
+ if (_th_stat.counter >= stat_interval_pkts) {
+ fieldstat_easy_counter_incrby(ctx->fse, thread_id,
+ ctx->fs_incoming_bytes_id, NULL, 0,
+ _th_stat.incoming_bytes);
+
+ fieldstat_easy_counter_incrby(ctx->fse, thread_id,
+ ctx->fs_incoming_pkts_id, NULL, 0,
+ _th_stat.incoming_pkts);
+
+ fieldstat_easy_counter_incrby(ctx->fse, thread_id,
+ ctx->fs_incoming_trans_id, NULL, 0,
+ _th_stat.incoming_trans);
+
+ fieldstat_easy_counter_incrby(ctx->fse, thread_id,
+ ctx->fs_err_pkts_id, NULL, 0,
+ _th_stat.err_pkts);
+
+ _th_stat.counter = 0;
+ _th_stat.err_pkts = 0;
+ _th_stat.incoming_bytes = 0;
+ _th_stat.incoming_pkts = 0;
+ _th_stat.incoming_trans = 0;
+ }
+}
+
+int http_decoder_entry(struct session *sess, int events,
+ const struct packet *pkt, void *cb_arg)
+{
+ struct http_decoder_context *ctx = (struct http_decoder_context *)cb_arg;
+ size_t payload_len = 0;
+ uint64_t inner_flag = 0;
+
+ int ret = session_is_inner_most(sess, &inner_flag);
+ if (0 == ret) {
+ return 0;
+ }
+
+ struct http_decoder_exdata *ex_data =
+ session_get_ex_data(sess, ctx->ex_data_idx);
+
+ if (events & SESS_EV_CLOSING) {
+ if (ex_data != NULL) {
+ http_decoder_exdata_free(ex_data);
+ session_set_ex_data(sess, ctx->ex_data_idx, NULL);
+ }
+
+ return 0;
+ }
+
+ const char *payload = session_get0_current_payload(sess, &payload_len);
+
+ if (events & SESS_EV_OPENING) {
+ assert(ex_data == NULL);
+
+ //If not http, ignore this session
+ if (payload_len > 0) {
+ size_t http_identify_len = payload_len > HTTP_IDENTIFY_LEN
+ ? HTTP_IDENTIFY_LEN
+ : payload_len;
+
+ ret = http_protocol_identify(payload, http_identify_len);
+ if (ret < 0) {
+ // ignore this session's event
+ struct session_event *s_event =
+ session_get_intrinsic_event(sess, ctx->plugin_id);
+
+ session_event_assign(s_event, ctx->st, sess, 0,
+ http_decoder_entry, ctx);
+ return 0;
+ }
+ }
+
+ ex_data = http_decoder_exdata_new(ctx->hd_cfg.mempool_size,
+ ctx->hd_cfg.result_queue_len,
+ ctx->hd_cfg.decompress_switch);
+ session_set_ex_data(sess, ctx->ex_data_idx, ex_data);
+ }
+
+ if (0 == payload_len || NULL == ex_data) {
+ return 0;
+ }
+
+ int dir = packet_get_direction(pkt);
+ if (dir < 0) {
+ return -1;
+ }
+
+ int thread_id = session_get_current_thread_id(sess);
+ struct http_decoder_half *cur_half = NULL;
+
+ if (dir == PACKET_DIRECTION_C2S) {
+ cur_half = ex_data->decoder->c2s_half;
+ } else {
+ cur_half = ex_data->decoder->s2c_half;
+ }
+
+ http_decoder_half_reinit(cur_half, ctx->topic_id, ex_data->queue,
+ ex_data->mempool, sess);
+ ret = http_decoder_half_parse(cur_half, payload, payload_len);
+ if (ret < 0) {
+ _th_stat.err_pkts += 1;
+ }
+
+ _th_stat.incoming_bytes += payload_len;
+ _th_stat.incoming_pkts += 1;
+ _th_stat.incoming_trans += http_decoder_half_trans_count(cur_half);
+ _th_stat.counter++;
+
+ http_decoder_stat_output(ctx, thread_id);
+
+ return 0;
+}
+
+static void _http_decoder_context_free(struct http_decoder_context *ctx)
+{
+ if (NULL == ctx) {
+ return;
+ }
+
+ if (ctx->fse != NULL) {
+ fieldstat_easy_free(ctx->fse);
+ ctx->fse = NULL;
+ }
+
+ if (ctx->topic_id >= 0) {
+ session_mq_destroy_topic(ctx->st, ctx->topic_id);
+ ctx->topic_id = -1;
+ }
+
+ FREE(ctx);
+}
+
+static void http_decoder_ex_data_free(struct session *s, int idx,
+ void *ex_data, void *arg)
+{
+ if (NULL == ex_data) {
+ return;
+ }
+
+ struct http_decoder_exdata *exdata = (struct http_decoder_exdata *)ex_data;
+ http_decoder_exdata_free(exdata);
+}
+
+static int load_http_decoder_config(const char *cfg_path,
+ struct http_decoder_config *hd_cfg)
+{
+ FILE *fp = fopen(cfg_path, "r");
+ if (NULL == fp) {
+ fprintf(stderr, "[%s:%d]Can't open config file:%s",
+ __FUNCTION__, __LINE__, cfg_path);
+ return -1;
+ }
+
+ int ret = 0;
+ char errbuf[256] = {0};
+
+ toml_table_t *root = toml_parse_file(fp, errbuf, sizeof(errbuf));
+ fclose(fp);
+
+ toml_table_t *basic_sec_tbl = toml_table_in(root, "basic");
+ if (NULL == basic_sec_tbl) {
+ fprintf(stderr, "[%s:%d]config file:%s has no key: [basic]",
+ __FUNCTION__, __LINE__, cfg_path);
+ ret = -1;
+ goto next;
+ }
+
+ toml_datum_t int_val = toml_int_in(basic_sec_tbl, "decompress");
+ if (int_val.ok != 0) {
+ hd_cfg->decompress_switch = int_val.u.b;
+ }
+
+ int_val = toml_int_in(basic_sec_tbl, "mempool_size");
+ if (int_val.ok != 0) {
+ hd_cfg->mempool_size = int_val.u.i;
+ } else {
+ hd_cfg->mempool_size = DEFAULT_MEMPOOL_SIZE;
+ }
+
+ int_val = toml_int_in(basic_sec_tbl, "result_queue_len");
+ if (int_val.ok != 0) {
+ hd_cfg->result_queue_len = int_val.u.i;
+ } else {
+ hd_cfg->result_queue_len = HD_RESULT_QUEUE_LEN;
+ }
+
+ int_val = toml_int_in(basic_sec_tbl, "stat_interval_pkts");
+ if (int_val.ok != 0) {
+ hd_cfg->stat_interval_pkts = int_val.u.i;
+ } else {
+ hd_cfg->stat_interval_pkts = DEFAULT_STAT_INTERVAL_PKTS;
+ }
+
+ int_val = toml_int_in(basic_sec_tbl, "stat_output_interval");
+ if (int_val.ok != 0) {
+ hd_cfg->stat_output_interval = int_val.u.i;
+ } else {
+ hd_cfg->stat_output_interval = DEFAULT_STAT_OUTPUT_INTERVAL;
+ }
+
+next:
+ toml_free(root);
+ return ret;
+}
+
+void *http_decoder_init(struct stellar *st)
+{
+ int plugin_id = -1;
+ int topic_id = -1;
+ int thread_num = 0;
+
+ struct http_decoder_context *ctx = CALLOC(struct http_decoder_context, 1);
+
+ int ret = load_http_decoder_config(g_hd_cfg_path, &ctx->hd_cfg);
+ if (ret < 0) {
+ goto failed;
+ }
+
+ ctx->st = st;
+ ctx->ex_data_idx = stellar_session_get_ex_new_index(st, "HTTP_DECODER",
+ http_decoder_ex_data_free,
+ NULL);
+
+ plugin_id = stellar_plugin_register(st, SESS_EV_TCP|SESS_EV_CLOSING,
+ http_decoder_entry, ctx);
+ if (plugin_id < 0) {
+ goto failed;
+ }
+
+ ctx->plugin_id = plugin_id;
+
+ topic_id = session_mq_get_topic_id(st, http_decoder_topic);
+ if (topic_id < 0) {
+ topic_id = session_mq_create_topic(st, http_decoder_topic,
+ http_message_free, NULL);
+ }
+
+ ctx->topic_id = topic_id;
+
+ thread_num = stellar_get_worker_thread_num(st);
+
+ if (http_decoder_stat_init(ctx, thread_num) < 0) {
+ goto failed;
+ }
+
+ printf("http_decoder_init: ex_data_idx:%d, plugin_id:%d, topic_id:%d\n",
+ ctx->ex_data_idx, ctx->plugin_id, ctx->topic_id);
+
+ return ctx;
+
+failed:
+ _http_decoder_context_free(ctx);
+ return NULL;
+}
+
+void http_decoder_exit(void *decoder_ctx)
+{
+ if (NULL == decoder_ctx) {
+ return;
+ }
+
+ struct http_decoder_context *ctx =
+ (struct http_decoder_context *)decoder_ctx;
+
+ _http_decoder_context_free(ctx);
+}
+
+enum http_message_type http_message_type(struct http_message *msg)
+{
+ if (NULL == msg) {
+ return HTTP_MESSAGE_MAX;
+ }
+
+ return msg->type;
+}
+
+int http_message_get_request_line(struct http_message *msg,
+ struct http_request_line *line)
+{
+ if (NULL == msg || msg->type != HTTP_MESSAGE_REQ_LINE ||
+ NULL == line) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *req_data =
+ msg->ref_queue->array[msg->queue_index].req_data;
+
+ return http_decoder_half_data_get_request_line(req_data, line);
+}
+
+int http_message_get_response_line(struct http_message *msg,
+ struct http_response_line *line)
+{
+ if (NULL == msg || msg->type != HTTP_MESSAGE_RES_LINE ||
+ NULL == line) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *res_data =
+ msg->ref_queue->array[msg->queue_index].res_data;
+
+ return http_decoder_half_data_get_response_line(res_data, line);
+}
+
+int http_message_get_request_header(struct http_message *msg, struct hstring *key,
+ struct http_header *hdr_array, size_t array_size)
+{
+ if (NULL == msg || msg->type != HTTP_MESSAGE_REQ_HEADER ||
+ NULL == key || NULL == hdr_array || 0 == array_size) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *req_data =
+ msg->ref_queue->array[msg->queue_index].req_data;
+
+ return http_decoder_half_data_get_header(req_data, key, hdr_array, array_size);
+}
+
+int http_message_get_response_header(struct http_message *msg, struct hstring *key,
+ struct http_header *hdr_array, size_t array_size)
+{
+ if (NULL == msg || msg->type != HTTP_MESSAGE_RES_HEADER || NULL == key ||
+ NULL == hdr_array || 0 == array_size) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *res_data =
+ msg->ref_queue->array[msg->queue_index].res_data;
+
+ return http_decoder_half_data_get_header(res_data, key, hdr_array, array_size);
+}
+
+int http_message_request_header_next(struct http_message *msg,
+ struct http_header *hdr)
+{
+ if (NULL == msg || msg->type != HTTP_MESSAGE_REQ_HEADER
+ || NULL == hdr) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *req_data =
+ msg->ref_queue->array[msg->queue_index].req_data;
+
+ return http_decoder_half_data_iter_header(req_data, hdr);
+}
+
+int http_message_response_header_next(struct http_message *msg,
+ struct http_header *hdr)
+{
+ if (NULL == msg || msg->type != HTTP_MESSAGE_RES_HEADER ||
+ NULL == hdr) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *res_data =
+ msg->ref_queue->array[msg->queue_index].res_data;
+
+ return http_decoder_half_data_iter_header(res_data, hdr);
+}
+
+int http_message_get_request_raw_body(struct http_message *msg,
+ struct hstring *body)
+{
+ if (NULL == msg || (msg->type != HTTP_MESSAGE_REQ_BODY) ||
+ NULL == body) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *req_data =
+ msg->ref_queue->array[msg->queue_index].req_data;
+
+ return http_decoder_half_data_get_raw_body(req_data, body);
+}
+
+int http_message_get_response_raw_body(struct http_message *msg,
+ struct hstring *body)
+{
+ if (NULL == msg || (msg->type != HTTP_MESSAGE_RES_BODY) ||
+ NULL == body) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *res_data =
+ msg->ref_queue->array[msg->queue_index].res_data;
+
+ return http_decoder_half_data_get_raw_body(res_data, body);
+}
+
+int http_message_get_request_decompress_body(struct http_message *msg,
+ struct hstring *body)
+{
+ if (NULL == msg || (msg->type != HTTP_MESSAGE_REQ_BODY) ||
+ NULL == body) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *req_data =
+ msg->ref_queue->array[msg->queue_index].req_data;
+
+ return http_decoder_half_data_get_decompress_body(req_data, body);
+}
+
+int http_message_get_response_decompress_body(struct http_message *msg,
+ struct hstring *body)
+{
+ if (NULL == msg || (msg->type != HTTP_MESSAGE_RES_BODY) ||
+ NULL == body) {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *res_data =
+ msg->ref_queue->array[msg->queue_index].res_data;
+
+ return http_decoder_half_data_get_decompress_body(res_data, body);
+}
+
+int http_message_get_url(struct http_message *msg, struct hstring *url)
+{
+ if (NULL == msg || NULL == url)
+ {
+ return -1;
+ }
+
+ assert(msg->ref_queue);
+ assert(msg->queue_index < HD_RESULT_QUEUE_LEN);
+
+ struct http_decoder_half_data *req_data =
+ msg->ref_queue->array[msg->queue_index].req_data;
+
+ return http_half_data_get_url(req_data, url);
+} \ No newline at end of file
diff --git a/src/http_decoder_half.c b/src/http_decoder_half.c
new file mode 100644
index 0000000..56a7838
--- /dev/null
+++ b/src/http_decoder_half.c
@@ -0,0 +1,1034 @@
+/*
+**********************************************************************************************
+* File: http_decoder_half.c
+* Description:
+* Authors: Liu WenTan <[email protected]>
+* Date: 2024-01-10
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "stellar/utils.h"
+#include "stellar/session_mq.h"
+#include "llhttp.h"
+#include "http_decoder.h"
+#include "http_decoder_inc.h"
+#include "http_decoder_utils.h"
+#include "http_decoder_half.h"
+#include "http_decoder_table.h"
+#include "http_content_decompress.h"
+
+struct http_decoder_half_data {
+ struct http_decoder_table *table;
+
+ int major_version;
+ int minor_version;
+ int status_code;
+
+ enum http_content_encoding content_encoding;
+ struct http_content_decompress *decompress;
+ char *ref_decompress_body;
+ size_t decompress_body_len;
+
+ int joint_url_complete;
+ struct hstring joint_url; // http://<host>[:<port>]/<path>?<searchpart>
+};
+
+struct http_decoder_half {
+ llhttp_t parser;
+ llhttp_settings_t settings;
+ enum llhttp_errno error;
+
+ int decompress_switch;
+
+ enum http_event event;
+ http_event_cb *http_ev_cb;
+ struct http_event_context *http_ev_ctx;
+
+ struct http_decoder_half_data *ref_data;
+
+ long long trans_counter;
+ long long err_counter;
+};
+
+// #define HTTP_DECODER_DEBUG
+#ifdef HTTP_DECODER_DEBUG
+static void printf_debug_info(const char *desc, const char *at, size_t length)
+{
+ if (at)
+ {
+ char *temp = safe_dup(at, length);
+ printf("HTTP PARSER STAGE: %s: %s\n", desc, temp);
+ FREE(temp);
+ }
+ else
+ {
+ printf("HTTP PARSER STAGE: %s\n", desc);
+ }
+}
+#else
+#define printf_debug_info(desc, at, length)
+#endif
+
+static void
+http_decoder_half_data_decompress(struct http_decoder_half_data *data)
+{
+ assert(data);
+
+ if (data->content_encoding == HTTP_CONTENT_ENCODING_NONE) {
+ return;
+ }
+
+ struct hstring raw_body = {0};
+ http_decoder_table_get_body(data->table, &raw_body);
+ if (raw_body.str == NULL || raw_body.str_len == 0) {
+ return;
+ }
+
+ if (NULL == data->decompress) {
+ data->decompress = http_content_decompress_create(data->content_encoding);
+ }
+
+ assert(data->decompress);
+ if (http_content_decompress_write(data->decompress, raw_body.str,
+ raw_body.str_len,
+ &data->ref_decompress_body,
+ &data->decompress_body_len) == -1) {
+ // log error
+ http_content_decompress_destroy(data->decompress);
+ data->decompress = NULL;
+ }
+}
+
+/* Possible return values 0, -1, `HPE_PAUSED` */
+static int on_message_begin(llhttp_t *http)
+{
+ printf_debug_info("on_message_begin", NULL, 0);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ if (half->parser.type == HTTP_REQUEST) {
+ half->event = HTTP_EVENT_REQ_INIT;
+ } else {
+ half->event = HTTP_EVENT_RES_INIT;
+ }
+
+ half->ref_data = NULL;
+
+ assert(half->http_ev_cb != NULL);
+ half->http_ev_cb(half->event, &half->ref_data, half->http_ev_ctx);
+
+ half->trans_counter++;
+
+ return 0;
+}
+
+static int on_message_complete(llhttp_t *http)
+{
+ printf_debug_info("on_message_complete", NULL, 0);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ if (half->parser.type == HTTP_REQUEST) {
+ if (half->event == HTTP_EVENT_REQ_BODY_DATA) {
+ half->event = HTTP_EVENT_REQ_BODY_END;
+ if (half->http_ev_cb != NULL) {
+ half->http_ev_cb(half->event, &half->ref_data,
+ half->http_ev_ctx);
+ }
+ }
+ } else {
+ if (half->event == HTTP_EVENT_RES_BODY_DATA) {
+ half->event = HTTP_EVENT_RES_BODY_END;
+ if (half->http_ev_cb != NULL) {
+ half->http_ev_cb(half->event, &half->ref_data,
+ half->http_ev_ctx);
+ }
+ }
+ }
+
+ //trigger req_end/res_end
+ if (half->parser.type == HTTP_REQUEST) {
+ half->event = HTTP_EVENT_REQ_END;
+ if (half->http_ev_cb != NULL) {
+ half->http_ev_cb(half->event, &half->ref_data, half->http_ev_ctx);
+ }
+ } else {
+ half->event = HTTP_EVENT_RES_END;
+ if (half->http_ev_cb != NULL) {
+ half->http_ev_cb(half->event, &half->ref_data, half->http_ev_ctx);
+ }
+ }
+
+ return 0;
+}
+
+static int on_reset(llhttp_t *http)
+{
+ printf_debug_info("on_reset", NULL, 0);
+
+ return 0;
+}
+
+static int on_method(llhttp_t *http, const char *at, size_t length)
+{
+ printf_debug_info("on_method", at, length);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ http_decoder_table_refer(half->ref_data->table, HTTP_ITEM_METHOD,
+ at, length);
+ return 0;
+}
+
+/* Information-only callbacks, return value is ignored */
+static int on_method_complete(llhttp_t *http)
+{
+ printf_debug_info("on_method_complete", NULL, 0);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_METHOD) ==
+ STRING_STATE_REFER) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_METHOD);
+ }
+
+ http_decoder_table_commit(half->ref_data->table, HTTP_ITEM_METHOD);
+
+ return 0;
+}
+
+/* Possible return values 0, -1, HPE_USER */
+static int on_uri(llhttp_t *http, const char *at, size_t length)
+{
+ printf_debug_info("on_uri", at, length);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ http_decoder_table_refer(half->ref_data->table, HTTP_ITEM_URI,
+ at, length);
+ return 0;
+}
+
+static void http_decoder_cached_portion_url(struct http_decoder_half *half, const struct hstring *uri_result)
+{
+ struct http_decoder_half_data *ref_data = half->ref_data;
+
+ ref_data->joint_url.str_len = uri_result->str_len;
+ ref_data->joint_url.str = MEMPOOL_CALLOC(half->http_ev_ctx->ref_mempool, char, uri_result->str_len);
+ // ref_data->joint_url.str = (char *)malloc(uri_result->str_len);
+ memcpy(ref_data->joint_url.str, uri_result->str, uri_result->str_len);
+}
+
+/* Information-only callbacks, return value is ignored */
+static int on_uri_complete(llhttp_t *http)
+{
+ printf_debug_info("on_uri_complete", NULL, 0);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_URI) ==
+ STRING_STATE_REFER) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_URI);
+ }
+
+ http_decoder_table_commit(half->ref_data->table, HTTP_ITEM_URI);
+
+ struct hstring uri_result = {};
+ http_decoder_table_get_uri(half->ref_data->table, &uri_result);
+ assert(uri_result.str);
+ http_decoder_cached_portion_url(half, &uri_result);
+
+ return 0;
+}
+
+/* Possible return values 0, -1, HPE_USER */
+static int on_version(llhttp_t *http, const char *at, size_t length)
+{
+ printf_debug_info("on_version", at, length);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ http_decoder_table_refer(half->ref_data->table, HTTP_ITEM_VERSION,
+ at, length);
+ return 0;
+}
+
+/* Information-only callbacks, return value is ignored */
+static int on_version_complete(llhttp_t *http)
+{
+ printf_debug_info("on_version_complete", NULL, 0);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_VERSION) ==
+ STRING_STATE_REFER) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_VERSION);
+ }
+
+ http_decoder_table_commit(half->ref_data->table, HTTP_ITEM_VERSION);
+
+ half->ref_data->major_version = llhttp_get_http_major(&half->parser);
+ half->ref_data->minor_version = llhttp_get_http_minor(&half->parser);
+
+ if (half->parser.type == HTTP_REQUEST) {
+ half->event = HTTP_EVENT_REQ_LINE;
+ if (half->http_ev_cb) {
+ half->http_ev_cb(half->event, &half->ref_data, half->http_ev_ctx);
+ }
+ }
+
+ return 0;
+}
+
+/* Possible return values 0, -1, HPE_USER */
+static int on_status(llhttp_t *http, const char *at, size_t length)
+{
+ printf_debug_info("on_status", at, length);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ http_decoder_table_refer(half->ref_data->table, HTTP_ITEM_STATUS,
+ at, length);
+ return 0;
+}
+
+/* Information-only callbacks, return value is ignored */
+static int on_status_complete(llhttp_t *http)
+{
+ printf_debug_info("on_status_complete", NULL, 0);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_STATUS) ==
+ STRING_STATE_REFER) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_STATUS);
+ }
+
+ http_decoder_table_commit(half->ref_data->table, HTTP_ITEM_STATUS);
+ half->ref_data->status_code = llhttp_get_status_code(&half->parser);
+
+ if (half->parser.type == HTTP_RESPONSE) {
+ half->event = HTTP_EVENT_RES_LINE;
+ if (half->http_ev_cb != NULL) {
+ half->http_ev_cb(half->event, &half->ref_data, half->http_ev_ctx);
+ }
+ }
+
+ return 0;
+}
+
+/* Possible return values 0, -1, HPE_USER */
+static int on_header_field(llhttp_t *http, const char *at, size_t length)
+{
+ printf_debug_info("on_header_field", at, length);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ http_decoder_table_refer(half->ref_data->table, HTTP_ITEM_HDRKEY,
+ at, length);
+ return 0;
+}
+
+/* Information-only callbacks, return value is ignored */
+static int on_header_field_complete(llhttp_t *http)
+{
+ printf_debug_info("on_header_field_complete", NULL, 0);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ http_decoder_table_commit(half->ref_data->table, HTTP_ITEM_HDRKEY);
+
+ return 0;
+}
+
+/* Possible return values 0, -1, HPE_USER */
+static int on_header_value(llhttp_t *http, const char *at, size_t length)
+{
+ printf_debug_info("on_header_value", at, length);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ http_decoder_table_refer(half->ref_data->table, HTTP_ITEM_HDRVAL,
+ at, length);
+ return 0;
+}
+
+#define MAX_ENCODING_STR_LEN 8
+/* Information-only callbacks, return value is ignored */
+static int on_header_value_complete(llhttp_t *http)
+{
+ printf_debug_info("on_header_value_complete", NULL, 0);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_HDRKEY) ==
+ STRING_STATE_CACHE) {
+ http_decoder_table_commit(half->ref_data->table, HTTP_ITEM_HDRKEY);
+ }
+
+ http_decoder_table_commit(half->ref_data->table, HTTP_ITEM_HDRVAL);
+
+ if (half->ref_data->content_encoding == HTTP_CONTENT_ENCODING_NONE) {
+ struct http_header http_hdr = {0};
+ struct hstring key = {.str = (char *)"Content-Encoding", .str_len = 16};
+
+ if (http_decoder_table_get_header(half->ref_data->table, &key,
+ &http_hdr, 1) == 1) {
+ char encoding_str[MAX_ENCODING_STR_LEN + 1] = {0};
+ size_t str_len = http_hdr.val.str_len;
+ if (str_len > MAX_ENCODING_STR_LEN) {
+ str_len = MAX_ENCODING_STR_LEN;
+ }
+ memcpy(encoding_str, http_hdr.val.str, str_len);
+ half->ref_data->content_encoding = http_content_encoding_str2int(encoding_str);
+ }
+ }
+
+ http_decoder_get_host_feed_url(half);
+
+ return 0;
+}
+
+/* When on_chunk_header is called, the current chunk length is stored
+ * in parser->content_length.
+ * Possible return values 0, -1, `HPE_PAUSED`
+ */
+static int on_chunk_header(llhttp_t *http)
+{
+ printf_debug_info("on_chunk_header", NULL, 0);
+
+ return 0;
+}
+
+/* When on_chunk_header is called, the current chunk length is stored
+ * in parser->content_length.
+ * Possible return values 0, -1, `HPE_PAUSED`
+ */
+static int on_chunk_header_complete(llhttp_t *http)
+{
+ printf_debug_info("on_chunk_header_complete", NULL, 0);
+
+ return 0;
+}
+
+/* Possible return values:
+ * 0 - Proceed normally
+ * 1 - Assume that request/response has no body, and proceed to parsing the next message
+ * 2 - Assume absence of body (as above) and make `llhttp_execute()` return `HPE_PAUSED_UPGRADE`
+ * -1 - Error `HPE_PAUSED`
+ */
+static int on_headers_complete(llhttp_t *http)
+{
+ printf_debug_info("on_headers_complete", NULL, 0);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+ assert(half->ref_data);
+
+ http_decoder_table_set_header_complete(half->ref_data->table);
+
+ if (half->parser.type == HTTP_REQUEST) {
+ half->event = HTTP_EVENT_REQ_HDR_END;
+ if (half->http_ev_cb) {
+ half->http_ev_cb(half->event, &half->ref_data, half->http_ev_ctx);
+ }
+ }
+
+ if (half->parser.type == HTTP_RESPONSE) {
+ half->event = HTTP_EVENT_RES_HDR_END;
+ if (half->http_ev_cb) {
+ half->http_ev_cb(half->event, &half->ref_data, half->http_ev_ctx);
+ }
+ }
+
+ return 0;
+}
+
+/* Possible return values 0, -1, HPE_USER */
+static int on_body(llhttp_t *http, const char *at, size_t length)
+{
+ printf_debug_info("on_body", at, length);
+
+ struct http_decoder_half *half =
+ container_of(http, struct http_decoder_half, parser);
+ assert(half);
+
+ // trigger body_begin event
+ if (half->parser.type == HTTP_REQUEST) {
+ if (half->event == HTTP_EVENT_REQ_HDR_END) {
+ half->event = HTTP_EVENT_REQ_BODY_BEGIN;
+ if (half->http_ev_cb) {
+ half->http_ev_cb(half->event, &half->ref_data,
+ half->http_ev_ctx);
+ }
+ }
+ } else {
+ if (half->event == HTTP_EVENT_RES_HDR_END) {
+ half->event = HTTP_EVENT_RES_BODY_BEGIN;
+ if (half->http_ev_cb) {
+ half->http_ev_cb(half->event, &half->ref_data,
+ half->http_ev_ctx);
+ }
+ }
+ }
+
+ if (half->ref_data != NULL) {
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_BODY) ==
+ STRING_STATE_COMMIT) {
+ http_decoder_table_reset(half->ref_data->table, HTTP_ITEM_BODY);
+ }
+
+ http_decoder_table_refer(half->ref_data->table, HTTP_ITEM_BODY,
+ at, length);
+ http_decoder_table_commit(half->ref_data->table, HTTP_ITEM_BODY);
+ }
+
+ if (1 == half->decompress_switch &&
+ half->ref_data->content_encoding != HTTP_CONTENT_ENCODING_NONE) {
+ http_decoder_half_data_decompress(half->ref_data);
+ }
+
+ if (half->parser.type == HTTP_REQUEST) {
+ half->event = HTTP_EVENT_REQ_BODY_DATA;
+ if (half->http_ev_cb) {
+ half->http_ev_cb(half->event, &half->ref_data, half->http_ev_ctx);
+ }
+ } else {
+ half->event = HTTP_EVENT_RES_BODY_DATA;
+ if (half->http_ev_cb) {
+ half->http_ev_cb(half->event, &half->ref_data, half->http_ev_ctx);
+ }
+ }
+
+ return 0;
+}
+
+static void
+http_decoder_half_init(struct http_decoder_half *half,
+ http_event_cb *http_ev_cb, int type)
+{
+ if (NULL == half) {
+ return;
+ }
+
+ llhttp_settings_init(&half->settings);
+ llhttp_init(&half->parser, type, &half->settings);
+
+ half->settings.on_message_begin = on_message_begin;
+ half->settings.on_message_complete = on_message_complete;
+ half->settings.on_reset = on_reset;
+
+ half->settings.on_url = on_uri;
+ half->settings.on_url_complete = on_uri_complete;
+
+ half->settings.on_status = on_status;
+ half->settings.on_status_complete = on_status_complete;
+
+ half->settings.on_method = on_method;
+ half->settings.on_method_complete = on_method_complete;
+
+ half->settings.on_version = on_version;
+ half->settings.on_version_complete = on_version_complete;
+
+ half->settings.on_header_field = on_header_field;
+ half->settings.on_header_field_complete = on_header_field_complete;
+
+ half->settings.on_header_value = on_header_value;
+ half->settings.on_header_value_complete = on_header_value_complete;
+
+ half->settings.on_chunk_header = on_chunk_header;
+ half->settings.on_chunk_complete = on_chunk_header_complete;
+
+ half->settings.on_headers_complete = on_headers_complete;
+ half->settings.on_body = on_body;
+
+ half->error = HPE_OK;
+
+ half->http_ev_cb = http_ev_cb;
+
+ half->ref_data = NULL;
+}
+
+struct http_decoder_half *
+http_decoder_half_new(nmx_pool_t *mempool, http_event_cb *ev_cb, int http_type,
+ int decompress_switch)
+{
+ struct http_decoder_half *half = MEMPOOL_CALLOC(mempool, struct http_decoder_half, 1);
+ assert(half);
+
+ half->decompress_switch = decompress_switch;
+ half->http_ev_ctx = MEMPOOL_CALLOC(mempool, struct http_event_context, 1);
+ http_decoder_half_init(half, ev_cb, http_type);
+
+ return half;
+}
+
+void http_decoder_half_free(nmx_pool_t *mempool, struct http_decoder_half *half)
+{
+ if (NULL == half) {
+ return;
+ }
+
+ if (half->http_ev_ctx != NULL) {
+ MEMPOOL_FREE(mempool, half->http_ev_ctx);
+ half->http_ev_ctx = NULL;
+ }
+
+ MEMPOOL_FREE(mempool, half);
+}
+
+void http_decoder_half_reinit(struct http_decoder_half *half, int topic_id,
+ struct http_decoder_result_queue *queue,
+ nmx_pool_t *mempool, struct session *sess)
+{
+ assert(half != NULL);
+
+ if (half->ref_data != NULL) {
+ http_decoder_table_reinit(half->ref_data->table);
+ }
+
+ half->http_ev_ctx->topic_id = topic_id;
+ half->http_ev_ctx->ref_mempool = mempool;
+ half->http_ev_ctx->ref_session = sess;
+ half->http_ev_ctx->ref_queue = queue;
+}
+
+static void publish_message_for_parsed_header(struct http_decoder_half *half)
+{
+ if (0 == http_decoder_table_has_parsed_header(half->ref_data->table)) {
+ return;
+ }
+
+ // publish complete kv-header message
+ struct http_message *msg = NULL;
+ size_t queue_idx = 0;
+ struct http_decoder_result_queue *queue = half->http_ev_ctx->ref_queue;
+
+ if (half->parser.type == HTTP_REQUEST) {
+ queue_idx = http_decoder_result_queue_req_index(queue);
+
+ msg = http_message_new(HTTP_MESSAGE_REQ_HEADER, queue, queue_idx);
+
+ session_mq_publish_message(half->http_ev_ctx->ref_session,
+ half->http_ev_ctx->topic_id, msg);
+ } else {
+ // http response
+ queue_idx = http_decoder_result_queue_res_index(queue);
+
+ msg = http_message_new(HTTP_MESSAGE_RES_HEADER, queue, queue_idx);
+
+ session_mq_publish_message(half->http_ev_ctx->ref_session,
+ half->http_ev_ctx->topic_id, msg);
+ }
+}
+
+int http_decoder_half_parse(struct http_decoder_half *half, const char *data,
+ size_t data_len)
+{
+ if (NULL == half || NULL == data || 0 == data_len) {
+ return -1;
+ }
+
+ half->error = llhttp_execute(&half->parser, data, data_len);
+
+ int ret = 0;
+ uint8_t type = 0;
+ struct http_decoder_half_data *half_data = NULL;
+
+ switch (half->error) {
+ case HPE_OK:
+ break;
+ case HPE_PAUSED:
+ llhttp_resume(&half->parser);
+ break;
+ case HPE_PAUSED_UPGRADE:
+ llhttp_resume_after_upgrade(&half->parser);
+ ret = 0;
+ break;
+ default:
+ type = half->parser.type;
+ llhttp_init(&half->parser, type, &half->settings);
+ ret = -1;
+ break;
+ }
+
+ if (ret < 0) {
+ // fprintf(stdout,
+ // "llhttp_execute parse error: %s err_reason:%s\n",
+ // llhttp_errno_name(half->error), half->parser.reason);
+ return half->error;
+ }
+
+ if (half->ref_data != NULL) {
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_URI)
+ == STRING_STATE_REFER) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_URI);
+ }
+
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_STATUS)
+ == STRING_STATE_REFER) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_STATUS);
+ }
+
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_METHOD)
+ == STRING_STATE_REFER) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_METHOD);
+ }
+
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_VERSION)
+ == STRING_STATE_REFER) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_VERSION);
+ }
+
+ if (http_decoder_table_header_complete(half->ref_data->table)) {
+ http_decoder_table_reset_header_complete(half->ref_data->table);
+ } else {
+ publish_message_for_parsed_header(half);
+ }
+
+ enum string_state hdr_key_state =
+ http_decoder_table_state(half->ref_data->table, HTTP_ITEM_HDRKEY);
+ enum string_state hdr_val_state =
+ http_decoder_table_state(half->ref_data->table, HTTP_ITEM_HDRVAL);
+
+ /* Truncated in http header key
+ For example http header k-v => User-Agent: Chrome
+ case1:
+ packet1: User- hdr_key_state == STRING_STATE_REFER
+ packet2: Agent: Chrome
+
+ case2:
+ packet1: User-Agent: hdr_key_state == STRING_STATE_COMMIT
+ hdr_val_state == STRING_STATE_INIT
+ packet2: Chrome
+ */
+ if (hdr_key_state == STRING_STATE_REFER ||
+ (hdr_key_state == STRING_STATE_COMMIT && hdr_val_state == STRING_STATE_INIT)) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_HDRKEY);
+ }
+
+ /* Truncated in http header value
+ For example http header k-v => User-Agent: Chrome
+ packet1: User-Agent: Ch hdr_key_state == STRING_STATE_COMMIT
+ hdr_val_state == STRING_STATE_REFER
+
+ packet2: rome
+ */
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_HDRVAL)
+ == STRING_STATE_REFER) {
+ /* Header key should have been committed
+ If it's not cached, cache it for next packet to use
+ */
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_HDRKEY);
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_HDRVAL);
+ }
+
+ if (http_decoder_table_state(half->ref_data->table, HTTP_ITEM_BODY)
+ == STRING_STATE_REFER) {
+ http_decoder_table_cache(half->ref_data->table, HTTP_ITEM_BODY);
+ }
+ }
+
+ return 0;
+}
+
+long long http_decoder_half_trans_count(struct http_decoder_half *half)
+{
+ if (NULL == half) {
+ return 0;
+ }
+
+ long long trans_cnt = half->trans_counter;
+ half->trans_counter = 0;
+
+ return trans_cnt;
+}
+
+struct http_decoder_half_data *
+http_decoder_half_data_new(nmx_pool_t *mempool)
+{
+ struct http_decoder_half_data *data =
+ MEMPOOL_CALLOC(mempool, struct http_decoder_half_data, 1);
+ assert(data);
+
+ data->table = http_decoder_table_new(mempool);
+ assert(data->table);
+
+ data->major_version = -1;
+ data->minor_version = -1;
+ data->status_code = -1;
+
+ data->content_encoding = HTTP_CONTENT_ENCODING_NONE;
+ data->ref_decompress_body = NULL;
+ data->decompress_body_len = 0;
+
+ return data;
+}
+
+void http_decoder_half_data_free(nmx_pool_t *mempool,
+ struct http_decoder_half_data *data)
+{
+ if (NULL == data) {
+ return;
+ }
+
+ if (data->table != NULL) {
+ http_decoder_table_free(data->table);
+ data->table = NULL;
+ }
+
+ if (data->decompress != NULL) {
+ http_content_decompress_destroy(data->decompress);
+ data->decompress = NULL;
+ }
+
+ if(data->joint_url.str)
+ {
+ MEMPOOL_FREE(mempool, data->joint_url.str);
+ data->joint_url.str = NULL;
+ data->joint_url_complete = 0;
+ }
+
+ MEMPOOL_FREE(mempool, data);
+}
+
+int http_decoder_half_data_get_request_line(struct http_decoder_half_data *data,
+ struct http_request_line *line)
+{
+ if (NULL == data || NULL == line) {
+ return -1;
+ }
+
+ http_decoder_table_get_method(data->table, &line->method);
+ http_decoder_table_get_uri(data->table, &line->uri);
+ http_decoder_table_get_version(data->table, &line->version);
+
+ line->major_version = data->major_version;
+ line->minor_version = data->minor_version;
+
+ return 0;
+}
+
+int http_decoder_half_data_get_response_line(struct http_decoder_half_data *data,
+ struct http_response_line *line)
+{
+ if (NULL == data || NULL == line) {
+ return -1;
+ }
+
+ http_decoder_table_get_version(data->table, &line->version);
+ http_decoder_table_get_status(data->table, &line->status);
+
+ line->major_version = data->major_version;
+ line->minor_version = data->minor_version;
+ line->status_code = data->status_code;
+
+ return 0;
+}
+
+int http_decoder_half_data_get_header(struct http_decoder_half_data *data,
+ struct hstring *key,
+ struct http_header *hdr_array,
+ size_t array_size)
+{
+ if (NULL == data || NULL == key ||
+ NULL == hdr_array || 0 == array_size) {
+ return -1;
+ }
+
+ return http_decoder_table_get_header(data->table, key, hdr_array, array_size);
+}
+
+int http_decoder_half_data_iter_header(struct http_decoder_half_data *data,
+ struct http_header *header)
+{
+ if (NULL == data || NULL == header) {
+ return -1;
+ }
+
+ return http_decoder_table_iter_header(data->table, header);
+}
+
+int http_decoder_half_data_has_parsed_header(struct http_decoder_half_data *data)
+{
+ if (NULL == data) {
+ return 0;
+ }
+
+ return http_decoder_table_has_parsed_header(data->table);
+}
+
+int http_decoder_half_data_get_raw_body(struct http_decoder_half_data *data,
+ struct hstring *body)
+{
+ if (NULL == data || NULL == body) {
+ return -1;
+ }
+
+ return http_decoder_table_get_body(data->table, body);
+}
+
+int http_decoder_half_data_get_decompress_body(struct http_decoder_half_data *data,
+ struct hstring *body)
+{
+ if (NULL == data || NULL == body) {
+ return -1;
+ }
+
+ if (HTTP_CONTENT_ENCODING_NONE == data->content_encoding) {
+ return http_decoder_table_get_body(data->table, body);
+ }
+
+
+ body->str = data->ref_decompress_body;
+ body->str_len = data->decompress_body_len;
+ return 0;
+}
+
+void http_decoder_half_data_dump(struct http_decoder_half *half)
+{
+ if (NULL == half || NULL == half->ref_data) {
+ return;
+ }
+
+ http_decoder_table_dump(half->ref_data->table);
+}
+
+static void using_session_addr_as_host(struct session *ref_session,
+ struct http_header *host_result, nmx_pool_t *mempool)
+{
+ const struct session_addr *ssaddr;
+ enum session_addr_type ssaddr_type;
+ ssaddr = session_get0_addr(ref_session, &ssaddr_type);
+ if (!ssaddr)
+ {
+ assert(0);
+ }
+
+ char ip_string_buf[INET6_ADDRSTRLEN];
+ if (SESSION_ADDR_TYPE_IPV4_TCP == ssaddr_type || SESSION_ADDR_TYPE_IPV4_UDP == ssaddr_type)
+ {
+ host_result->val.str = MEMPOOL_CALLOC(mempool, char, (INET_ADDRSTRLEN + 7) /* "ip:port" max length */);
+ inet_ntop(AF_INET, &ssaddr->ipv4.daddr, ip_string_buf, INET_ADDRSTRLEN);
+ sprintf(host_result->val.str, "%s:%u", ip_string_buf, ntohs(ssaddr->ipv4.dport));
+ host_result->val.str_len = strlen(host_result->val.str);
+ }
+ else if (SESSION_ADDR_TYPE_IPV6_TCP == ssaddr_type || SESSION_ADDR_TYPE_IPV6_UDP == ssaddr_type)
+ {
+ host_result->val.str = MEMPOOL_CALLOC(mempool, char, (INET6_ADDRSTRLEN + 7) /* "ip:port" max length */);
+ inet_ntop(AF_INET6, &ssaddr->ipv6.daddr, ip_string_buf, INET6_ADDRSTRLEN);
+ sprintf(host_result->val.str, "%s:%u", ip_string_buf, ntohs(ssaddr->ipv6.dport));
+ host_result->val.str_len = strlen(host_result->val.str);
+ }
+ else
+ {
+ assert(0);
+ }
+}
+
+void http_decoder_join_url(struct http_decoder_half_data *hfdata, nmx_pool_t *mempool, const struct http_header *host_hdr)
+{
+ //int url_cache_str_len = strlen("http://") + host_hdr->val.str_len + hfdata->joint_url.str_len;
+ int url_cache_str_len = host_hdr->val.str_len + hfdata->joint_url.str_len;
+ char *url_cache_str = MEMPOOL_CALLOC(mempool, char, url_cache_str_len);
+
+ char *ptr = url_cache_str;
+ //memcpy(ptr, "http://", strlen("http://"));
+ //ptr += strlen("http://");
+ memcpy(ptr, host_hdr->val.str, host_hdr->val.str_len);
+ ptr += host_hdr->val.str_len;
+ memcpy(ptr, hfdata->joint_url.str, hfdata->joint_url.str_len);
+
+ MEMPOOL_FREE(mempool, hfdata->joint_url.str); // free the cached uri buffer
+ hfdata->joint_url.str = url_cache_str;
+ hfdata->joint_url.str_len = url_cache_str_len;
+
+ hfdata->joint_url_complete = 1;
+}
+
+int http_decoder_join_url_finally(struct http_event_context *ev_ctx,
+ struct http_decoder_half_data *hfdata,
+ nmx_pool_t *mempool)
+{
+ struct http_header addr_as_host = {};
+
+ if (hfdata->joint_url_complete)
+ {
+ return 0;
+ }
+
+ using_session_addr_as_host(ev_ctx->ref_session, &addr_as_host, mempool);
+ http_decoder_join_url(hfdata, mempool, &addr_as_host);
+ MEMPOOL_FREE(mempool, addr_as_host.val.str); // free session addr to host buffer
+ return 1;
+}
+
+void http_decoder_get_host_feed_url(struct http_decoder_half *half)
+{
+ struct http_header host_result = {};
+ struct hstring host_key = {"Host", 4};
+ const char *host_refer_str = NULL;
+ int host_refer_len = 0;
+
+ if (half->ref_data->joint_url_complete)
+ {
+ return;
+ }
+
+ int host_header_cnt = http_decoder_half_data_get_header(half->ref_data, &host_key,
+ &host_result, 1);
+ if (host_header_cnt <= 0)
+ {
+ return;
+ }
+
+ http_decoder_join_url(half->ref_data, half->http_ev_ctx->ref_mempool, &host_result);
+}
+
+int http_half_data_get_url(struct http_decoder_half_data *res_data, struct hstring *url)
+{
+ if (0 == res_data->joint_url_complete)
+ {
+ return -1;
+ }
+
+ url->str = res_data->joint_url.str;
+ url->str_len = res_data->joint_url.str_len;
+
+ return 0;
+} \ No newline at end of file
diff --git a/src/http_decoder_half.h b/src/http_decoder_half.h
new file mode 100644
index 0000000..0fcd0b6
--- /dev/null
+++ b/src/http_decoder_half.h
@@ -0,0 +1,119 @@
+/*
+**********************************************************************************************
+* File: http_decoder_half.h
+* Description:
+* Authors: Liu WenTan <[email protected]>
+* Date: 2024-01-10
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+
+#ifndef _HTTP_DECODER_HALF_H_
+#define _HTTP_DECODER_HALF_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stddef.h>
+
+#include "stellar/session.h"
+#include "http_decoder.h"
+#include "http_content_decompress.h"
+#include "http_decoder_result_queue.h"
+
+// only one http event is fired at a time
+enum http_event {
+ HTTP_EVENT_REQ_INIT = 1 << 1,
+ HTTP_EVENT_REQ_LINE = 1 << 2,
+ HTTP_EVENT_REQ_HDR = 1 << 3,
+ HTTP_EVENT_REQ_HDR_END = 1 << 4,
+ HTTP_EVENT_REQ_BODY_BEGIN = 1 << 5,
+ HTTP_EVENT_REQ_BODY_DATA = 1 << 6,
+ HTTP_EVENT_REQ_BODY_END = 1 << 7,
+ HTTP_EVENT_REQ_END = 1 << 8,
+
+ HTTP_EVENT_RES_INIT = 1 << 9,
+ HTTP_EVENT_RES_LINE = 1 << 10,
+ HTTP_EVENT_RES_HDR = 1 << 11,
+ HTTP_EVENT_RES_HDR_END = 1 << 12,
+ HTTP_EVENT_RES_BODY_BEGIN = 1 << 13,
+ HTTP_EVENT_RES_BODY_DATA = 1 << 14,
+ HTTP_EVENT_RES_BODY_END = 1 << 15,
+ HTTP_EVENT_RES_END = 1 << 16,
+};
+
+struct http_event_context {
+ int topic_id;
+ nmx_pool_t *ref_mempool;
+ struct session *ref_session;
+ struct http_decoder_result_queue *ref_queue;
+};
+
+struct http_decoder_half;
+struct http_decoder_half_data;
+
+typedef void http_event_cb(enum http_event event, struct http_decoder_half_data **data,
+ struct http_event_context *ev_ctx);
+
+struct http_decoder_half *
+http_decoder_half_new(nmx_pool_t *mempool, http_event_cb *event_cb, int http_type,
+ int decompress_switch);
+
+void http_decoder_half_free(nmx_pool_t *mempool, struct http_decoder_half *half);
+
+void http_decoder_half_reinit(struct http_decoder_half *half, int topic_id,
+ struct http_decoder_result_queue *queue,
+ nmx_pool_t *mempool, struct session *sess);
+
+int http_decoder_half_parse(struct http_decoder_half *half, const char *data,
+ size_t data_len);
+
+long long http_decoder_half_trans_count(struct http_decoder_half *half);
+
+//http decoder half data API
+struct http_decoder_half_data *
+http_decoder_half_data_new(nmx_pool_t *mempool);
+
+void http_decoder_half_data_free(nmx_pool_t *mempool,
+ struct http_decoder_half_data *data);
+
+int http_decoder_half_data_get_request_line(struct http_decoder_half_data *data,
+ struct http_request_line *line);
+
+int http_decoder_half_data_get_response_line(struct http_decoder_half_data *data,
+ struct http_response_line *line);
+
+int http_decoder_half_data_get_header(struct http_decoder_half_data *data,
+ struct hstring *key, struct http_header *hdr_array,
+ size_t array_size);
+
+int http_decoder_half_data_iter_header(struct http_decoder_half_data *data,
+ struct http_header *header);
+
+int http_decoder_half_data_has_parsed_header(struct http_decoder_half_data *data);
+
+int http_decoder_half_data_get_raw_body(struct http_decoder_half_data *data,
+ struct hstring *body);
+
+int http_decoder_half_data_get_decompress_body(struct http_decoder_half_data *data,
+ struct hstring *body);
+
+void http_decoder_half_data_dump(struct http_decoder_half *half);
+
+void http_decoder_get_host_feed_url(struct http_decoder_half *half);
+void http_decoder_join_url(struct http_decoder_half_data *hfdata,
+ nmx_pool_t *mempool,
+ const struct http_header *host_hdr);
+int http_decoder_join_url_finally(struct http_event_context *ev_ctx,
+ struct http_decoder_half_data *hfdata,
+ nmx_pool_t *mempool);
+int http_half_data_get_url(struct http_decoder_half_data *res_data, struct hstring *url);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/src/http_decoder_inc.h b/src/http_decoder_inc.h
new file mode 100644
index 0000000..2d6a5c0
--- /dev/null
+++ b/src/http_decoder_inc.h
@@ -0,0 +1,53 @@
+/*
+**********************************************************************************************
+* File: http_decoder_inc.h
+* Description:
+* Authors: Liu WenTan <[email protected]>
+* Date: 2024-01-10
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+#ifndef _HTTP_DECODER_INC_H_
+#define _HTTP_DECODER_INC_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "mempool/nmx_palloc.h"
+#include "stellar/utils.h"
+#include "http_decoder.h"
+#include "http_decoder_result_queue.h"
+
+
+#define MEMPOOL_CALLOC(pool, type, number) ((type *)nmx_pcalloc(pool, sizeof(type) * number))
+#define MEMPOOL_REALLOC(pool)
+#define MEMPOOL_FREE(pool, p) nmx_pfree(pool, p)
+
+#ifdef ENABLE_MEMPOOL
+
+#define HD_CALLOC(pool, type, number) MEMPOOL_CALLOC(pool, number, type)
+#define HD_FREE(pool, p) MEMPOOL_FREE(pool, p)
+
+#else
+
+#define HD_CALLOC(pool, type, number) CALLOC(type, number)
+#define HD_FREE(pool, p) FREE(p)
+
+#endif
+
+
+struct http_message;
+
+struct http_message *
+http_message_new(enum http_message_type type,
+ struct http_decoder_result_queue *queue,
+ int queue_index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/src/http_decoder_result_queue.c b/src/http_decoder_result_queue.c
new file mode 100644
index 0000000..9a60d15
--- /dev/null
+++ b/src/http_decoder_result_queue.c
@@ -0,0 +1,172 @@
+/*
+**********************************************************************************************
+* File: http_decoder_result_queue.c
+* Description:
+* Authors: Liuwentan <[email protected]>
+* Date: 2024-01-15
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+#include <assert.h>
+
+#include "stellar/utils.h"
+#include "http_decoder_half.h"
+#include "http_decoder_inc.h"
+#include "http_decoder_result_queue.h"
+
+struct http_decoder_result_queue *
+http_decoder_result_queue_new(nmx_pool_t *mempool, size_t queue_size)
+{
+ struct http_decoder_result_queue *queue =
+ MEMPOOL_CALLOC(mempool, struct http_decoder_result_queue, 1);
+ assert(queue);
+
+ queue->req_index = 0;
+ queue->res_index = 0;
+ queue->queue_size = queue_size;
+
+ queue->array = MEMPOOL_CALLOC(mempool, struct http_decoder_result,
+ queue->queue_size);
+ assert(queue->array);
+
+ return queue;
+}
+
+void http_decoder_result_queue_free(nmx_pool_t *mempool,
+ struct http_decoder_result_queue *queue)
+{
+ if (NULL == queue) {
+ return;
+ }
+
+ if (queue->array != NULL) {
+ for (size_t i = 0; i < queue->queue_size; i++) {
+ if (queue->array[i].req_data != NULL) {
+ http_decoder_half_data_free(mempool, queue->array[i].req_data);
+ queue->array[i].req_data = NULL;
+ }
+
+ if (queue->array[i].res_data != NULL) {
+ http_decoder_half_data_free(mempool, queue->array[i].res_data);
+ queue->array[i].res_data = NULL;
+ }
+ }
+
+ MEMPOOL_FREE(mempool, queue->array);
+ }
+
+ MEMPOOL_FREE(mempool, queue);
+}
+
+void http_decoder_result_queue_inc_req_index(struct http_decoder_result_queue *queue)
+{
+ assert(queue);
+
+ queue->req_index++;
+ queue->req_index = queue->req_index % queue->queue_size;
+}
+
+void http_decoder_result_queue_inc_res_index(struct http_decoder_result_queue *queue)
+{
+ assert(queue);
+
+ queue->res_index++;
+ queue->res_index = queue->res_index % queue->queue_size;
+}
+
+size_t http_decoder_result_queue_req_index(struct http_decoder_result_queue *queue)
+{
+ assert(queue);
+
+ return queue->req_index;
+}
+
+size_t http_decoder_result_queue_res_index(struct http_decoder_result_queue *queue)
+{
+ assert(queue);
+
+ return queue->res_index;
+}
+
+int http_decoder_result_queue_push_req(struct http_decoder_result_queue *queue,
+ struct http_decoder_half_data *req_data)
+{
+ if (NULL == queue || NULL == req_data) {
+ return -1;
+ }
+
+ assert(queue->array[queue->req_index].req_data == NULL);
+ if (queue->array[queue->req_index].req_data != NULL) {
+ return -1;
+ }
+
+ queue->array[queue->req_index].req_data = req_data;
+ return 0;
+}
+
+int http_decoder_result_queue_push_res(struct http_decoder_result_queue *queue,
+ struct http_decoder_half_data *res_data)
+{
+ if (NULL == queue || NULL == res_data) {
+ return -1;
+ }
+
+ assert(queue->array[queue->res_index].res_data == NULL);
+ if (queue->array[queue->res_index].res_data != NULL) {
+ return -1;
+ }
+
+ queue->array[queue->res_index].res_data = res_data;
+ return 0;
+}
+
+struct http_decoder_half_data *
+http_decoder_result_queue_pop_req(struct http_decoder_result_queue *queue)
+{
+ if (NULL == queue) {
+ return NULL;
+ }
+
+ struct http_decoder_half_data *req_data =
+ queue->array[queue->req_index].req_data;
+ queue->array[queue->req_index].req_data = NULL;
+
+ return req_data;
+}
+
+struct http_decoder_half_data *
+http_decoder_result_queue_pop_res(struct http_decoder_result_queue *queue)
+{
+ if (NULL == queue) {
+ return NULL;
+ }
+
+ struct http_decoder_half_data *res_data =
+ queue->array[queue->res_index].res_data;
+ queue->array[queue->res_index].res_data = NULL;
+
+ return res_data;
+}
+
+struct http_decoder_half_data *
+http_decoder_result_queue_peek_req(struct http_decoder_result_queue *queue)
+{
+ if (NULL == queue) {
+ return NULL;
+ }
+
+ assert(queue->req_index < queue->queue_size);
+ return queue->array[queue->req_index].req_data;
+}
+
+struct http_decoder_half_data *
+http_decoder_result_queue_peek_res(struct http_decoder_result_queue *queue)
+{
+ if (NULL == queue) {
+ return NULL;
+ }
+
+ assert(queue->res_index < queue->queue_size);
+ return queue->array[queue->res_index].res_data;
+} \ No newline at end of file
diff --git a/src/http_decoder_result_queue.h b/src/http_decoder_result_queue.h
new file mode 100644
index 0000000..cd2163a
--- /dev/null
+++ b/src/http_decoder_result_queue.h
@@ -0,0 +1,73 @@
+/*
+**********************************************************************************************
+* File: http_decoder_result_queue.h
+* Description:
+* Authors: Liuwentan <[email protected]>
+* Date: 2024-01-15
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+
+#ifndef _HTTP_DECODER_RESULT_QUEUE_H_
+#define _HTTP_DECODER_RESULT_QUEUE_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stddef.h>
+
+#include "mempool/nmx_palloc.h"
+#include "http_decoder_half.h"
+
+struct http_decoder_result {
+ struct http_decoder_half_data *req_data;
+ struct http_decoder_half_data *res_data;
+};
+
+struct http_decoder_result_queue {
+ size_t req_index;
+ size_t res_index;
+ size_t queue_size;
+ struct http_decoder_result *array;
+};
+
+struct http_decoder_result_queue *
+http_decoder_result_queue_new(nmx_pool_t *mempool, size_t queue_size);
+
+void http_decoder_result_queue_free(nmx_pool_t *mempool,
+ struct http_decoder_result_queue *queue);
+
+void http_decoder_result_queue_inc_req_index(struct http_decoder_result_queue *queue);
+
+void http_decoder_result_queue_inc_res_index(struct http_decoder_result_queue *queue);
+
+size_t http_decoder_result_queue_req_index(struct http_decoder_result_queue *queue);
+
+size_t http_decoder_result_queue_res_index(struct http_decoder_result_queue *queue);
+
+struct http_decoder_half_data *
+http_decoder_result_queue_pop_req(struct http_decoder_result_queue *queue);
+
+struct http_decoder_half_data *
+http_decoder_result_queue_pop_res(struct http_decoder_result_queue *queue);
+
+int http_decoder_result_queue_push_req(struct http_decoder_result_queue *queue,
+ struct http_decoder_half_data *req_data);
+
+int http_decoder_result_queue_push_res(struct http_decoder_result_queue *queue,
+ struct http_decoder_half_data *res_data);
+
+struct http_decoder_half_data *
+http_decoder_result_queue_peek_req(struct http_decoder_result_queue *queue);
+
+struct http_decoder_half_data *
+http_decoder_result_queue_peek_res(struct http_decoder_result_queue *queue);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/src/http_decoder_string.c b/src/http_decoder_string.c
new file mode 100644
index 0000000..5414c5b
--- /dev/null
+++ b/src/http_decoder_string.c
@@ -0,0 +1,277 @@
+/*
+**********************************************************************************************
+* File: http_decoder_string.h
+* Description:
+* Authors: LuWenPeng <[email protected]>
+* Date: 2022-10-31
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "stellar/utils.h"
+#include "http_decoder_utils.h"
+#include "http_decoder_string.h"
+
+static const char *string_state_to_desc(enum string_state state)
+{
+ switch (state) {
+ case STRING_STATE_INIT:
+ return "init";
+ break;
+ case STRING_STATE_REFER:
+ return "refer";
+ break;
+ case STRING_STATE_CACHE:
+ return "cache";
+ break;
+ case STRING_STATE_COMMIT:
+ return "commit";
+ break;
+ default:
+ return "unknown";
+ break;
+ }
+}
+
+void http_decoder_string_refer(struct http_decoder_string *rstr,
+ const char *at, size_t length)
+{
+ if (NULL == rstr) {
+ return;
+ }
+
+ switch (rstr->state) {
+ case STRING_STATE_INIT:
+ case STRING_STATE_CACHE:
+ rstr->refer.str = (char *)at;
+ rstr->refer.str_len = length;
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ rstr->state = STRING_STATE_REFER;
+}
+
+static void string_refer2cache(struct http_decoder_string *rstr)
+{
+ if (0 == rstr->refer.str_len) {
+ return;
+ }
+
+ if (rstr->cache.str_len >= rstr->max_cache_size) {
+ return;
+ }
+
+ size_t length = rstr->cache.str_len + rstr->refer.str_len;
+ if (length > rstr->max_cache_size) {
+ length = rstr->max_cache_size;
+ }
+
+ if (NULL == rstr->cache.str) {
+ rstr->cache.str = CALLOC(char, length + 1);
+ memcpy(rstr->cache.str, rstr->refer.str, length);
+ } else {
+ rstr->cache.str = REALLOC(char, rstr->cache.str, length + 1);
+ memcpy(rstr->cache.str + rstr->cache.str_len, rstr->refer.str,
+ (length - rstr->cache.str_len));
+ }
+
+ rstr->cache.str_len = length;
+
+ rstr->refer.str = NULL;
+ rstr->refer.str_len = 0;
+}
+
+static void string_commit2cache(struct http_decoder_string *rstr)
+{
+ if (rstr->cache.str_len == rstr->commit.str_len &&
+ rstr->cache.str == rstr->commit.str) {
+
+ rstr->commit.str = NULL;
+ rstr->commit.str_len = 0;
+ return;
+ }
+
+ //Only http header key need to backward to cache
+ size_t length = 0;
+ if (rstr->commit.str_len > rstr->max_cache_size) {
+ length = rstr->max_cache_size;
+ } else {
+ length = rstr->commit.str_len;
+ }
+
+ if (length > 0) {
+ if (NULL == rstr->cache.str) {
+ rstr->cache.str = CALLOC(char, length + 1);
+ } else {
+ abort();
+ }
+ memcpy(rstr->cache.str, rstr->commit.str, length);
+ rstr->cache.str_len = length;
+
+ rstr->commit.str = NULL;
+ rstr->commit.str_len = 0;
+ }
+}
+
+void http_decoder_string_cache(struct http_decoder_string *rstr)
+{
+ if (NULL == rstr) {
+ return;
+ }
+
+ switch (rstr->state) {
+ case STRING_STATE_REFER:
+ string_refer2cache(rstr);
+ break;
+ case STRING_STATE_CACHE:
+ break;
+ case STRING_STATE_COMMIT:
+ //commit backward to cache
+ string_commit2cache(rstr);
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ rstr->state = STRING_STATE_CACHE;
+}
+
+void http_decoder_string_commit(struct http_decoder_string *rstr)
+{
+ if (NULL == rstr) {
+ return;
+ }
+
+ switch (rstr->state) {
+ case STRING_STATE_REFER:
+ if (rstr->cache.str_len) {
+ http_decoder_string_cache(rstr);
+
+ rstr->commit.str = rstr->cache.str;
+ rstr->commit.str_len = rstr->cache.str_len;
+ // not overwrite rstr->cache.str
+ } else {
+ rstr->commit.str = rstr->refer.str;
+ rstr->commit.str_len = rstr->refer.str_len;
+
+ rstr->refer.str = NULL;
+ rstr->refer.str_len = 0;
+ }
+ break;
+ case STRING_STATE_CACHE:
+ rstr->commit.str = rstr->cache.str;
+ rstr->commit.str_len = rstr->cache.str_len;
+ // not overwrite rstr->cache.str
+ break;
+ default:
+ //abort();
+ break;
+ }
+
+ rstr->state = STRING_STATE_COMMIT;
+}
+
+void http_decoder_string_reset(struct http_decoder_string *rstr)
+{
+ assert(rstr);
+
+ switch (rstr->state) {
+ case STRING_STATE_INIT:
+ case STRING_STATE_REFER:
+ case STRING_STATE_CACHE:
+ case STRING_STATE_COMMIT:
+ FREE(rstr->cache.str);
+ memset(rstr, 0, sizeof(struct http_decoder_string));
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ rstr->state = STRING_STATE_INIT;
+}
+
+
+void http_decoder_string_init(struct http_decoder_string *rstr,
+ size_t max_cache_size)
+{
+ rstr->max_cache_size = max_cache_size;
+}
+
+void http_decoder_string_reinit(struct http_decoder_string *rstr)
+{
+ if (rstr->state == STRING_STATE_CACHE) {
+ return;
+ }
+
+ if (rstr->state == STRING_STATE_COMMIT &&
+ rstr->cache.str == rstr->commit.str &&
+ rstr->cache.str_len == rstr->commit.str_len) {
+ return;
+ }
+
+ if (rstr->cache.str != NULL) {
+ FREE(rstr->cache.str);
+ rstr->cache.str_len = 0;
+ }
+
+ rstr->refer.str = NULL;
+ rstr->refer.str_len = 0;
+
+ rstr->commit.str = NULL;
+ rstr->commit.str_len = 0;
+
+ rstr->state = STRING_STATE_INIT;
+}
+
+enum string_state http_decoder_string_state(struct http_decoder_string *rstr)
+{
+ return rstr->state;
+}
+
+int http_decoder_string_get(struct http_decoder_string *rstr, struct hstring *out)
+{
+ if (NULL == rstr || NULL == out) {
+ return -1;
+ }
+
+ if (http_decoder_string_state(rstr) == STRING_STATE_COMMIT) {
+ out->str = rstr->commit.str;
+ out->str_len = rstr->commit.str_len;
+ } else {
+ out->str = NULL;
+ out->str_len = 0;
+ }
+
+ return 0;
+}
+
+void http_decoder_string_dump(struct http_decoder_string *rstr, const char *desc)
+{
+ if (NULL == rstr) {
+ return;
+ }
+
+ char *refer_str = safe_dup(rstr->refer.str, rstr->refer.str_len);
+ char *cache_str = safe_dup(rstr->cache.str, rstr->cache.str_len);
+ char *commit_str = safe_dup(rstr->commit.str, rstr->commit.str_len);
+
+ printf("%s: state: %s, refer: {len: %02zu, str: %s}, cache: {len: %02zu, str: %s}, commit: {len: %02zu, str: %s}\n",
+ desc, string_state_to_desc(rstr->state),
+ rstr->refer.str_len, refer_str,
+ rstr->cache.str_len, cache_str,
+ rstr->commit.str_len, commit_str);
+
+ FREE(refer_str);
+ FREE(cache_str);
+ FREE(commit_str);
+} \ No newline at end of file
diff --git a/src/http_decoder_string.h b/src/http_decoder_string.h
new file mode 100644
index 0000000..f9d81dd
--- /dev/null
+++ b/src/http_decoder_string.h
@@ -0,0 +1,95 @@
+/*
+**********************************************************************************************
+* File: http_decoder_string.h
+* Description:
+* Authors: LuWenPeng <[email protected]>
+* Date: 2022-10-31
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+#ifndef _HTTP_DECODER_STRING_H_
+#define _HTTP_DECODER_STRING_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "http_decoder.h"
+
+
+enum string_state {
+ STRING_STATE_INIT,
+ STRING_STATE_REFER,
+ STRING_STATE_CACHE,
+ STRING_STATE_COMMIT,
+};
+
+/* state transition diagram
+ * +----------+
+ * | |
+ * \|/ |
+ * +------+ |
+ * | init | |
+ * +------+ |
+ * | |
+ * +---->| |
+ * | \|/ |
+ * | +-------+ |
+ * | | refer |--+ |
+ * | +-------+ | |
+ * | | | |
+ * | \|/ | |
+ * | +-------+ | |
+ * +--| cache | | |
+ * +-------+ | |
+ * | | |
+ * |<------+ |
+ * \|/ |
+ * +--------+ |
+ * | commit | |
+ * +--------+ |
+ * | |
+ * \|/ |
+ * +--------+ |
+ * | reset |----+
+ * +--------+
+ */
+
+
+//http decoder string
+struct http_decoder_string {
+ struct hstring refer; // shallow copy
+ struct hstring cache; // deep copy
+ struct hstring commit;
+
+ enum string_state state;
+ size_t max_cache_size;
+};
+
+void http_decoder_string_refer(struct http_decoder_string *rstr,
+ const char *at, size_t length);
+
+void http_decoder_string_cache(struct http_decoder_string *rstr);
+
+void http_decoder_string_commit(struct http_decoder_string *rstr);
+
+void http_decoder_string_reset(struct http_decoder_string *rstr);
+
+void http_decoder_string_init(struct http_decoder_string *rstr,
+ size_t max_cache_size);
+
+void http_decoder_string_reinit(struct http_decoder_string *rstr);
+
+enum string_state http_decoder_string_state(struct http_decoder_string *rstr);
+
+int http_decoder_string_get(struct http_decoder_string *rstr, struct hstring *out);
+
+void http_decoder_string_dump(struct http_decoder_string *rstr, const char *desc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/src/http_decoder_table.c b/src/http_decoder_table.c
new file mode 100644
index 0000000..ede7989
--- /dev/null
+++ b/src/http_decoder_table.c
@@ -0,0 +1,556 @@
+/*
+**********************************************************************************************
+* File: http_decoder_table.c
+* Description:
+* Authors: LuWenPeng <[email protected]>
+* Date: 2022-10-31
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "http_decoder_table.h"
+#include "http_decoder.h"
+#include "http_decoder_string.h"
+#include "stellar/utils.h"
+
+#define INIT_HEADER_CNT 16
+#define MAX_URI_CACHE_SIZE 2048
+#define MAX_STATUS_CACHE_SIZE 32
+#define MAX_METHOD_CACHE_SIZE 8
+#define MAX_VERSION_CACHE_SIZE 4
+#define MAX_HEADER_KEY_CACHE_SIZE 4096
+#define MAX_HEADER_VALUE_CACHE_SIZE 4096
+
+struct http_decoder_header {
+ struct http_decoder_string key;
+ struct http_decoder_string val;
+};
+
+struct http_decoder_table {
+ struct http_decoder_string uri;
+ struct http_decoder_string status;
+ struct http_decoder_string method;
+ struct http_decoder_string version;
+ struct http_decoder_string body;
+
+ nmx_pool_t *ref_mempool;
+ int header_complete; // flag for all headers parsed completely
+ size_t header_cnt;
+ size_t header_index;
+ size_t header_iter;
+ struct http_decoder_header *headers;
+};
+
+static void http_decoder_table_init(struct http_decoder_table *table)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ struct http_decoder_header *header = NULL;
+ assert(table);
+
+ http_decoder_string_init(&table->uri, MAX_URI_CACHE_SIZE);
+ http_decoder_string_init(&table->status, MAX_STATUS_CACHE_SIZE);
+ http_decoder_string_init(&table->method, MAX_METHOD_CACHE_SIZE);
+ http_decoder_string_init(&table->version, MAX_METHOD_CACHE_SIZE);
+
+ for (size_t i = 0; i < table->header_cnt; i++) {
+ header = &table->headers[i];
+ http_decoder_string_init(&header->key, MAX_HEADER_KEY_CACHE_SIZE);
+ http_decoder_string_init(&header->val, MAX_HEADER_VALUE_CACHE_SIZE);
+ }
+
+ http_decoder_string_init(&table->body, 0);
+}
+
+struct http_decoder_table *http_decoder_table_new(nmx_pool_t *mempool)
+{
+ struct http_decoder_table *table =
+ MEMPOOL_CALLOC(mempool, struct http_decoder_table, 1);
+ assert(table);
+
+ table->ref_mempool = mempool;
+ table->header_cnt = INIT_HEADER_CNT;
+ table->headers = MEMPOOL_CALLOC(mempool, struct http_decoder_header,
+ table->header_cnt);
+
+ http_decoder_table_init(table);
+
+ return table;
+}
+
+void http_decoder_table_free(struct http_decoder_table *table)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ if (table->uri.cache.str != NULL) {
+ FREE(table->uri.cache.str);
+ }
+
+ if (table->status.cache.str != NULL) {
+ FREE(table->status.cache.str);
+ }
+
+ if (table->method.cache.str != NULL) {
+ FREE(table->method.cache.str);
+ }
+
+ if (table->version.cache.str != NULL) {
+ FREE(table->version.cache.str);
+ }
+
+ if (table->body.cache.str != NULL) {
+ FREE(table->body.cache.str);
+ }
+
+ if (table->headers != NULL) {
+ for (size_t i = 0; i < table->header_cnt; i++) {
+ if (table->headers[i].key.cache.str != NULL) {
+ FREE(table->headers[i].key.cache.str);
+ }
+
+ if (table->headers[i].val.cache.str != NULL) {
+ FREE(table->headers[i].val.cache.str);
+ }
+ }
+
+ MEMPOOL_FREE(table->ref_mempool, table->headers);
+ table->headers = NULL;
+ }
+
+ MEMPOOL_FREE(table->ref_mempool, table);
+}
+
+enum string_state
+http_decoder_table_state(struct http_decoder_table *table, enum http_item type)
+{
+ if (NULL == table) {
+ return STRING_STATE_INIT;
+ }
+
+ struct http_decoder_header *header = NULL;
+ enum string_state state = STRING_STATE_INIT;
+ assert(table);
+
+ switch (type) {
+ case HTTP_ITEM_URI:
+ state = http_decoder_string_state(&table->uri);
+ break;
+ case HTTP_ITEM_STATUS:
+ state = http_decoder_string_state(&table->status);
+ break;
+ case HTTP_ITEM_METHOD:
+ state = http_decoder_string_state(&table->method);
+ break;
+ case HTTP_ITEM_VERSION:
+ state = http_decoder_string_state(&table->version);
+ break;
+ case HTTP_ITEM_HDRKEY:
+ assert(table->header_index < table->header_cnt);
+ header = &table->headers[table->header_index];
+ state = http_decoder_string_state(&header->key);
+ break;
+ case HTTP_ITEM_HDRVAL:
+ assert(table->header_index < table->header_cnt);
+ header = &table->headers[table->header_index];
+ state = http_decoder_string_state(&header->val);
+ break;
+ case HTTP_ITEM_BODY:
+ state = http_decoder_string_state(&table->body);
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ return state;
+}
+
+void http_decoder_table_refer(struct http_decoder_table *table, enum http_item type,
+ const char *at, size_t len)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ struct http_decoder_header *header = NULL;
+ assert(table);
+
+ switch (type) {
+ case HTTP_ITEM_URI:
+ http_decoder_string_refer(&table->uri, at, len);
+ break;
+ case HTTP_ITEM_STATUS:
+ http_decoder_string_refer(&table->status, at, len);
+ break;
+ case HTTP_ITEM_METHOD:
+ http_decoder_string_refer(&table->method, at, len);
+ break;
+ case HTTP_ITEM_VERSION:
+ http_decoder_string_refer(&table->version, at, len);
+ break;
+ case HTTP_ITEM_HDRKEY:
+ assert(table->header_index < table->header_cnt);
+ header = &table->headers[table->header_index];
+ http_decoder_string_refer(&header->key, at, len);
+ break;
+ case HTTP_ITEM_HDRVAL:
+ assert(table->header_index < table->header_cnt);
+ header = &table->headers[table->header_index];
+ http_decoder_string_refer(&header->val, at, len);
+ break;
+ case HTTP_ITEM_BODY:
+ http_decoder_string_refer(&table->body, at, len);
+ break;
+ default:
+ abort();
+ break;
+ }
+}
+
+void http_decoder_table_cache(struct http_decoder_table *table, enum http_item type)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ struct http_decoder_header *header = NULL;
+ assert(table);
+
+ switch (type) {
+ case HTTP_ITEM_URI:
+ http_decoder_string_cache(&table->uri);
+ break;
+ case HTTP_ITEM_STATUS:
+ http_decoder_string_cache(&table->status);
+ break;
+ case HTTP_ITEM_METHOD:
+ http_decoder_string_cache(&table->method);
+ break;
+ case HTTP_ITEM_VERSION:
+ http_decoder_string_cache(&table->version);
+ break;
+ case HTTP_ITEM_HDRKEY:
+ assert(table->header_index < table->header_cnt);
+ header = &table->headers[table->header_index];
+ http_decoder_string_cache(&header->key);
+ break;
+ case HTTP_ITEM_HDRVAL:
+ assert(table->header_index < table->header_cnt);
+ header = &table->headers[table->header_index];
+ http_decoder_string_cache(&header->val);
+ break;
+ case HTTP_ITEM_BODY:
+ http_decoder_string_cache(&table->body);
+ break;
+ default:
+ abort();
+ break;
+ }
+}
+
+void http_decoder_table_commit(struct http_decoder_table *table, enum http_item type)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ size_t i = 0;
+ struct http_decoder_header *header = NULL;
+ assert(table);
+
+ switch (type) {
+ case HTTP_ITEM_URI:
+ http_decoder_string_commit(&table->uri);
+ break;
+ case HTTP_ITEM_STATUS:
+ http_decoder_string_commit(&table->status);
+ break;
+ case HTTP_ITEM_METHOD:
+ http_decoder_string_commit(&table->method);
+ break;
+ case HTTP_ITEM_VERSION:
+ http_decoder_string_commit(&table->version);
+ break;
+ case HTTP_ITEM_HDRKEY:
+ assert(table->header_index < table->header_cnt);
+ header = &table->headers[table->header_index];
+ http_decoder_string_commit(&header->key);
+ break;
+ case HTTP_ITEM_HDRVAL:
+ header = &table->headers[table->header_index];
+ http_decoder_string_commit(&header->val);
+ // inc index
+ if ((table->header_index + 1) >= table->header_cnt) {
+ struct http_decoder_header *old_headers = table->headers;
+ table->headers =
+ MEMPOOL_CALLOC(table->ref_mempool, struct http_decoder_header,
+ table->header_cnt * 2);
+ table->header_cnt *= 2;
+
+ for (i = 0; i <= table->header_index; i++) {
+ table->headers[i] = old_headers[i];
+ }
+
+ MEMPOOL_FREE(table->ref_mempool, old_headers);
+
+ for (i = table->header_index + 1; i < table->header_cnt; i++) {
+ header = &table->headers[i];
+ memset(header, 0, sizeof(struct http_decoder_header));
+ http_decoder_string_init(&header->key, MAX_HEADER_KEY_CACHE_SIZE);
+ http_decoder_string_init(&header->val, MAX_HEADER_VALUE_CACHE_SIZE);
+ }
+ }
+ table->header_index++;
+ break;
+ case HTTP_ITEM_BODY:
+ http_decoder_string_commit(&table->body);
+ break;
+ default:
+ abort();
+ break;
+ }
+}
+
+void http_decoder_table_reset(struct http_decoder_table *table, enum http_item type)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ struct http_decoder_header *header = NULL;
+ assert(table);
+
+ switch (type) {
+ case HTTP_ITEM_URI:
+ http_decoder_string_reset(&table->uri);
+ break;
+ case HTTP_ITEM_STATUS:
+ http_decoder_string_reset(&table->status);
+ break;
+ case HTTP_ITEM_METHOD:
+ http_decoder_string_reset(&table->method);
+ break;
+ case HTTP_ITEM_VERSION:
+ http_decoder_string_reset(&table->version);
+ break;
+ case HTTP_ITEM_HDRKEY:
+ header = &table->headers[table->header_index];
+ http_decoder_string_reset(&header->key);
+ break;
+ case HTTP_ITEM_HDRVAL:
+ header = &table->headers[table->header_index];
+ http_decoder_string_reset(&header->val);
+ break;
+ case HTTP_ITEM_BODY:
+ http_decoder_string_reset(&table->body);
+ break;
+ default:
+ abort();
+ break;
+ }
+}
+
+void http_decoder_table_reinit(struct http_decoder_table *table)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ struct http_decoder_header *header = NULL;
+ assert(table);
+
+ http_decoder_string_reinit(&table->uri);
+ http_decoder_string_reinit(&table->status);
+ http_decoder_string_reinit(&table->method);
+ http_decoder_string_reinit(&table->version);
+ for (size_t i = 0; i < table->header_iter; i++) {
+ header = &table->headers[i];
+ http_decoder_string_reinit(&header->key);
+ http_decoder_string_reinit(&header->val);
+ }
+
+ http_decoder_string_reinit(&table->body);
+}
+
+void http_decoder_table_dump(struct http_decoder_table *table)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ http_decoder_string_dump(&table->uri, "uri");
+ http_decoder_string_dump(&table->status, "status");
+ http_decoder_string_dump(&table->method, "method");
+ http_decoder_string_dump(&table->version, "version");
+ http_decoder_string_dump(&table->body, "body");
+
+ for (size_t i = 0; i < table->header_cnt; i++) {
+ struct http_decoder_header *header = &table->headers[i];
+ if (NULL == header) {
+ continue;
+ }
+
+ http_decoder_string_dump(&header->key, "key");
+ http_decoder_string_dump(&header->val, "val");
+ }
+}
+
+int http_decoder_table_get_uri(struct http_decoder_table *table, struct hstring *out)
+{
+ if (NULL == table || NULL == out) {
+ return -1;
+ }
+
+ return http_decoder_string_get(&table->uri, out);
+}
+
+int http_decoder_table_get_method(struct http_decoder_table *table, struct hstring *out)
+{
+ if (NULL == table || NULL == out) {
+ return -1;
+ }
+
+ return http_decoder_string_get(&table->method, out);
+}
+
+int http_decoder_table_get_status(struct http_decoder_table *table, struct hstring *out)
+{
+ if (NULL == table || NULL == out) {
+ return -1;
+ }
+
+ return http_decoder_string_get(&table->status, out);
+}
+
+int http_decoder_table_get_version(struct http_decoder_table *table, struct hstring *out)
+{
+ if (NULL == table || NULL == out) {
+ return -1;
+ }
+
+ return http_decoder_string_get(&table->version, out);
+}
+
+int http_decoder_table_get_body(struct http_decoder_table *table, struct hstring *out)
+{
+ if (NULL == table || NULL == out) {
+ return -1;
+ }
+
+ return http_decoder_string_get(&table->body, out);
+}
+
+int http_decoder_table_get_header(struct http_decoder_table *table, struct hstring *key,
+ struct http_header *hdr_array, size_t array_size)
+{
+ if (NULL == table || NULL == key->str || 0 == key->str_len) {
+ return 0;
+ }
+
+ int header_cnt = 0;
+ for (size_t i = 0; i < table->header_cnt && header_cnt < array_size; i++) {
+ struct http_decoder_header *tmp_header = &table->headers[i];
+ if (tmp_header->key.commit.str_len != key->str_len) {
+ continue;
+ }
+
+ if (http_decoder_string_state(&tmp_header->key) == STRING_STATE_COMMIT &&
+ http_decoder_string_state(&tmp_header->val) == STRING_STATE_COMMIT) {
+ struct hstring tmp_key;
+ http_decoder_string_get(&tmp_header->key, &tmp_key);
+
+ if (tmp_key.str_len == key->str_len &&
+ (0 == strncasecmp(tmp_key.str, key->str, key->str_len))) {
+ http_decoder_string_get(&tmp_header->key, &hdr_array[header_cnt].key);
+ http_decoder_string_get(&tmp_header->val, &hdr_array[header_cnt].val);
+ header_cnt++;
+ }
+ }
+ }
+
+ return header_cnt;
+}
+
+int http_decoder_table_iter_header(struct http_decoder_table *table,
+ struct http_header *hdr)
+{
+ if (NULL == table || NULL == hdr) {
+ return -1;
+ }
+
+ if (table->header_iter >= table->header_cnt) {
+ return 0;
+ }
+
+ struct http_decoder_header *tmp_header = &table->headers[table->header_iter];
+ if (tmp_header != NULL) {
+ if (http_decoder_string_state(&tmp_header->key) == STRING_STATE_COMMIT &&
+ http_decoder_string_state(&tmp_header->val) == STRING_STATE_COMMIT) {
+
+ http_decoder_string_get(&tmp_header->key, &hdr->key);
+ http_decoder_string_get(&tmp_header->val, &hdr->val);
+ table->header_iter++;
+
+ return 1;
+ }
+ }
+
+ hdr->key.str = NULL;
+ hdr->key.str_len = 0;
+
+ hdr->val.str = NULL;
+ hdr->val.str_len = 0;
+
+ return 0;
+}
+
+int http_decoder_table_has_parsed_header(struct http_decoder_table *table)
+{
+ if (NULL == table || (table->header_iter == table->header_index)) {
+ return 0;
+ }
+
+ struct http_decoder_header *tmp_header = &table->headers[table->header_iter];
+ if (http_decoder_string_state(&tmp_header->key) == STRING_STATE_COMMIT &&
+ http_decoder_string_state(&tmp_header->val) == STRING_STATE_COMMIT) {
+ return 1;
+ }
+
+ return 0;
+}
+
+int http_decoder_table_header_complete(struct http_decoder_table *table)
+{
+ if (NULL == table) {
+ return -1;
+ }
+
+ return table->header_complete;
+}
+
+void http_decoder_table_set_header_complete(struct http_decoder_table *table)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ table->header_complete = 1;
+}
+
+void http_decoder_table_reset_header_complete(struct http_decoder_table *table)
+{
+ if (NULL == table) {
+ return;
+ }
+
+ table->header_complete = 0;
+} \ No newline at end of file
diff --git a/src/http_decoder_table.h b/src/http_decoder_table.h
new file mode 100644
index 0000000..fe443bb
--- /dev/null
+++ b/src/http_decoder_table.h
@@ -0,0 +1,99 @@
+/*
+**********************************************************************************************
+* File: http_decoder_table.h
+* Description:
+* Authors: LuWenPeng <[email protected]>
+* Date: 2022-10-31
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+
+#ifndef _HTTP_DECODER_TABLE_H_
+#define _HTTP_DECODER_TABLE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+
+#include "http_decoder.h"
+#include "http_decoder_inc.h"
+#include "http_decoder_string.h"
+
+enum http_item {
+ HTTP_ITEM_URI = 0x01,
+ HTTP_ITEM_STATUS = 0x02,
+ HTTP_ITEM_METHOD = 0x03,
+ HTTP_ITEM_VERSION = 0x04,
+ HTTP_ITEM_HDRKEY = 0x05,
+ HTTP_ITEM_HDRVAL = 0x06,
+ HTTP_ITEM_BODY = 0x07,
+};
+
+struct http_decoder_table;
+struct http_decoder_table *http_decoder_table_new(nmx_pool_t *mempool);
+
+void http_decoder_table_free(struct http_decoder_table *table);
+
+enum string_state
+http_decoder_table_state(struct http_decoder_table *table, enum http_item type);
+
+void http_decoder_table_refer(struct http_decoder_table *table, enum http_item type,
+ const char *at, size_t len);
+
+void http_decoder_table_cache(struct http_decoder_table *table, enum http_item type);
+
+void http_decoder_table_commit(struct http_decoder_table *table, enum http_item type);
+
+void http_decoder_table_reset(struct http_decoder_table *table, enum http_item type);
+
+void http_decoder_table_reinit(struct http_decoder_table *table);
+
+void http_decoder_table_dump(struct http_decoder_table *table);
+
+int http_decoder_table_get_uri(struct http_decoder_table *table, struct hstring *out);
+
+int http_decoder_table_get_method(struct http_decoder_table *table, struct hstring *out);
+
+int http_decoder_table_get_status(struct http_decoder_table *table, struct hstring *out);
+
+int http_decoder_table_get_version(struct http_decoder_table *table, struct hstring *out);
+
+int http_decoder_table_get_body(struct http_decoder_table *table, struct hstring *out);
+
+int http_decoder_table_get_header(struct http_decoder_table *table,
+ struct hstring *key,
+ struct http_header *hdr_array,
+ size_t array_size);
+
+int http_decoder_table_iter_header(struct http_decoder_table *table,
+ struct http_header *hdr);
+
+/**
+ * @brief Is there a parsed header
+ *
+ * @retval yes(1) no(0)
+*/
+int http_decoder_table_has_parsed_header(struct http_decoder_table *table);
+
+/**
+ * @brief If headers have been parsed completely
+ *
+ * @retval yes(1) no(0)
+ */
+int http_decoder_table_header_complete(struct http_decoder_table *table);
+
+/**
+ * @brief set flag for headers parsed completely
+*/
+void http_decoder_table_set_header_complete(struct http_decoder_table *table);
+
+void http_decoder_table_reset_header_complete(struct http_decoder_table *table);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/src/http_decoder_utils.c b/src/http_decoder_utils.c
new file mode 100644
index 0000000..a5dfbe1
--- /dev/null
+++ b/src/http_decoder_utils.c
@@ -0,0 +1,25 @@
+/*
+**********************************************************************************************
+* File: http_decoder_utils.c
+* Description:
+* Authors: LuWenPeng <[email protected]>
+* Date: 2022-10-31
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+#include <string.h>
+
+#include "stellar/utils.h"
+
+char *safe_dup(const char *str, size_t len)
+{
+ if (str == NULL || len == 0) {
+ return NULL;
+ }
+
+ char *dup = CALLOC(char, len + 1);
+ memcpy(dup, str, len);
+
+ return dup;
+} \ No newline at end of file
diff --git a/src/http_decoder_utils.h b/src/http_decoder_utils.h
new file mode 100644
index 0000000..9c031a3
--- /dev/null
+++ b/src/http_decoder_utils.h
@@ -0,0 +1,66 @@
+/*
+**********************************************************************************************
+* File: http_decoder_utils.h
+* Description:
+* Authors: LuWenPeng <[email protected]>
+* Date: 2022-10-31
+* Copyright: (c) Since 2022 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+
+#ifndef _HTTP_DECODER_UTILS_H_
+#define _HTTP_DECODER_UTILS_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+
+
+char *safe_dup(const char *str, size_t len);
+
+/******************************************************************************
+ * Logger
+ ******************************************************************************/
+
+enum http_decoder_log_level {
+ DEBUG = 0x11,
+ WARN = 0x12,
+ INFO = 0x13,
+ ERROR = 0x14,
+};
+
+#ifndef http_decoder_log
+#define http_decoder_log(level, format, ...) \
+ { \
+ switch (level) \
+ { \
+ case DEBUG: \
+ fprintf(stdout, "HTTP_DECODER [DEBUG] " format "\n", ##__VA_ARGS__); \
+ fflush(stdout); \
+ break; \
+ case WARN: \
+ fprintf(stdout, "HTTP_DECODER [WARN] " format "\n", ##__VA_ARGS__); \
+ fflush(stdout); \
+ break; \
+ case INFO: \
+ fprintf(stdout, "HTTP_DECODER [INFO] " format "\n", ##__VA_ARGS__); \
+ fflush(stdout); \
+ break; \
+ case ERROR: \
+ fprintf(stderr, "HTTP_DECODER [ERROR] " format "\n", ##__VA_ARGS__); \
+ fflush(stderr); \
+ break; \
+ } \
+ }
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/src/version.map b/src/version.map
new file mode 100644
index 0000000..d26b83a
--- /dev/null
+++ b/src/version.map
@@ -0,0 +1,9 @@
+VERS_3.0{
+global:
+ extern "C" {
+ http_message_*;
+ http_decoder_init;
+ http_decoder_entry;
+ };
+local: *;
+}; \ No newline at end of file
diff --git a/test/.gitkeep b/test/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/.gitkeep
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..6ee5c6f
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,149 @@
+set(DECODER_NAME http_decoder)
+
+add_library(${DECODER_NAME}_test SHARED http_decoder_gtest.cpp)
+add_dependencies(${DECODER_NAME}_test ${DECODER_NAME})
+target_link_libraries(${DECODER_NAME}_test MESA_prof_load)
+set_target_properties(${DECODER_NAME}_test PROPERTIES PREFIX "")
+
+set(TEST_RUN_DIR ${CMAKE_BINARY_DIR}/testing)
+
+include_directories(${CMAKE_SOURCE_DIR}/include)
+include_directories(/usr/local/include/cjson)
+include_directories(/opt/tsg/framework/include/stellar)
+include_directories(/opt/MESA/include/MESA)
+
+add_executable(gtest_http_decoder http_decoder_driver.cpp http_decoder_stub.cpp http_decoder_gtest.cpp)
+link_directories(${CMAKE_BINARY_DIR}/src)
+target_link_libraries(gtest_http_decoder http_decoder gtest pcap MESA_jump_layer cjson-static)
+
+set(TEST_MAIN gtest_http_decoder)
+
+add_test(NAME CREATE_RUN_ENV COMMAND sh -c "mkdir -p ${TEST_RUN_DIR}")
+add_test(NAME COPY_TEST_MAIN COMMAND sh -c "cp ${CMAKE_BINARY_DIR}/test/${TEST_MAIN} ${TEST_RUN_DIR}/${TEST_MAIN}")
+add_test(NAME COPY_HTTP_DECODER_CONF COMMAND sh -c "mkdir -p ${TEST_RUN_DIR}/etc/http && cp ${PROJECT_SOURCE_DIR}/conf/http_decoder.toml ${TEST_RUN_DIR}/etc/http/")
+
+
+set_tests_properties(CREATE_RUN_ENV COPY_TEST_MAIN COPY_HTTP_DECODER_CONF
+ PROPERTIES FIXTURES_SETUP TestFixture)
+
+# run tests
+add_test(NAME HTTP_GET_SINGLE_TRANS_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_get_single_trans.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_get_single_trans.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_GET_SINGLE_TRANS_MSS1_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_get_single_trans.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_get_single_trans.pcap -m 1 WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_GET_MULTI_TRANS_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_get_multi_trans.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_get_multi_trans.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_GET_LONG_COOKIE_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_get_long_cookie.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_get_long_cookie.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_GET_ENCODED_URI_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_get_encoded_uri.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_get_encoded_uri.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_RES_GZIP_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_res_gzip.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_res_gzip.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_CHUNKED_RES_GZIP_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_chunked_res_gzip.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_chunked_res_gzip.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_OVER_TCP_KEEPALIVE_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_over_tcp_keepalive.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_over_tcp_keepalive.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_TUNNEL_FOR_POP3_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_tunnel_for_pop3.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_tunnel_for_pop3.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_OVER_PPPOE_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_over_pppoe.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_over_pppoe.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_OVER_TLS_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_over_tls.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_over_tls.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME NON_HTTP_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/non_http.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/non_http.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_REQ_1BYTE_SLIDING_WINDOW_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_req_1byte_sliding_window.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_req_1byte_sliding_window.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_RES_1BYTE_SLIDING_WINDOW_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_res_1byte_sliding_window.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_res_1byte_sliding_window.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_NO_CONTENT_LENGTH_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_no_content_length.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_no_content_length.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_POST_MULTIPART_FORM_DATA_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_post_multipart_form_data.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_post_multipart_form_data.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_HEADERS_EXCEED_MAXIMUM_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_hdrs_exceed_maximum.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_hdrs_exceed_maximum.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+#add_test(NAME HTTP_CONNECT_FLOOD_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_connect_flood.json
+# -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_connect_flood.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_GET_MALFORMED_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_get_malformed.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_get_malformed.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_HEADER_VALUE_EMPTY_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_hdr_value_empty.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_hdr_value_empty.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_UPGRADE_WEBSOCKET_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_upgrade_websocket.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_upgrade_websocket.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_UPGRADE_HTTP2_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_upgrade_http2.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_upgrade_http2.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_MULTI_PARSE_ERROR_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_multi_parse_error.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_multi_parse_error.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_GET_REQ_PIPELINE_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_get_req_pipeline.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_get_req_pipeline.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_TRANS_PIPELINE_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_trans_pipeline.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_trans_pipeline.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_HEADER_TRUNCATED_IN_KV_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_hdr_truncated_in_kv.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_hdr_truncated_in_kv.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_HEADER_TRUNCATED_AFTER_KV_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_hdr_truncated_after_kv.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_hdr_truncated_after_kv.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_URL_WITH_HOST_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_url_test_with_host.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_url_test_with_host.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_URL_WITHOUT_HOST_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_url_test_without_host.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_url_test_without_host.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+add_test(NAME HTTP_6OVER4_SINGLE_TRANS_TEST COMMAND ${TEST_MAIN} -b ${CMAKE_CURRENT_SOURCE_DIR}/test_result_json/http_6over4_single_trans.json
+ -p ${CMAKE_CURRENT_SOURCE_DIR}/http_pcap/http_6over4_single_trans.pcap WORKING_DIRECTORY ${TEST_RUN_DIR})
+
+set_tests_properties(HTTP_GET_SINGLE_TRANS_TEST
+ HTTP_GET_SINGLE_TRANS_MSS1_TEST
+ HTTP_GET_MULTI_TRANS_TEST
+ HTTP_GET_LONG_COOKIE_TEST
+ HTTP_GET_ENCODED_URI_TEST
+ HTTP_RES_GZIP_TEST
+ HTTP_CHUNKED_RES_GZIP_TEST
+ HTTP_OVER_TCP_KEEPALIVE_TEST
+ HTTP_TUNNEL_FOR_POP3_TEST
+ HTTP_OVER_PPPOE_TEST
+ HTTP_OVER_TLS_TEST
+ NON_HTTP_TEST
+ HTTP_REQ_1BYTE_SLIDING_WINDOW_TEST
+ HTTP_RES_1BYTE_SLIDING_WINDOW_TEST
+ HTTP_NO_CONTENT_LENGTH_TEST
+ HTTP_POST_MULTIPART_FORM_DATA_TEST
+ HTTP_HEADERS_EXCEED_MAXIMUM_TEST
+ HTTP_GET_MALFORMED_TEST
+ HTTP_HEADER_VALUE_EMPTY_TEST
+ HTTP_MULTI_PARSE_ERROR_TEST
+ HTTP_UPGRADE_WEBSOCKET_TEST
+ HTTP_UPGRADE_HTTP2_TEST
+ HTTP_GET_REQ_PIPELINE_TEST
+ HTTP_TRANS_PIPELINE_TEST
+ HTTP_HEADER_TRUNCATED_IN_KV_TEST
+ HTTP_HEADER_TRUNCATED_AFTER_KV_TEST
+ HTTP_URL_WITH_HOST_TEST
+ HTTP_URL_WITHOUT_HOST_TEST
+ HTTP_6OVER4_SINGLE_TRANS_TEST
+ PROPERTIES FIXTURES_REQUIRED TestFixture)
diff --git a/test/http_decoder_driver.cpp b/test/http_decoder_driver.cpp
new file mode 100644
index 0000000..b1e24dc
--- /dev/null
+++ b/test/http_decoder_driver.cpp
@@ -0,0 +1,923 @@
+/*
+ Http Decoder Google Test driver module
+*/
+#include <stdio.h>
+#include <string.h>
+#ifndef __USE_MISC
+#define __USE_MISC 1
+#endif
+#ifndef __FAVOR_BSD
+#define __FAVOR_BSD 1
+#endif
+#ifndef __USE_BSD
+#define __USE_BSD 1
+#endif
+#include <netinet/tcp.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <time.h>
+#include <unistd.h>
+#include <assert.h>
+#include <getopt.h>
+#include <gtest/gtest.h>
+#include <pcap/pcap.h>
+#include "http_decoder_gtest.h"
+#include "MESA_jump_layer.h"
+
+extern "C" int http_decoder_entry(struct session *sess, int events,
+ const struct packet *pkt, void *cb_arg);
+extern "C" void http_decoder_test_exit(void *test_ctx);
+extern "C" void *http_decoder_test_init(struct stellar *st);
+extern "C" void *http_decoder_init(struct stellar *st);
+struct fake_stellar *g_fake_stellar; // used for plugin commit_test_result_json()
+
+static const char *hdgt_cla_short_options = "hb:p:s:m:";
+static const struct option hdgt_cla_long_options[] =
+ {
+ {"help", no_argument, NULL, 'h'},
+ {"benchmark-file", required_argument, NULL, 'b'},
+ {"data-pcap-file", required_argument, NULL, 'p'},
+ {"data-json-file", required_argument, NULL, 's'},
+ {"mss", required_argument, NULL, 'm'},
+ {NULL, 0, NULL, 0}};
+
+static const char *g_data_src_json_non_headers[] = {
+ "__X_HTTP_TUPLE4",
+ "__X_HTTP_TRANSACTION",
+ "__X_HTTP_RESULT_INDEX",
+ "__X_HTTP_URL",
+ "method",
+ "uri",
+ "req_version",
+ "major_version",
+ "minor_version",
+ "res_version",
+ "res_status",
+ "status_code",
+ NULL};
+
+static void hdgt_cmd_usage(int argc, char **argv)
+{
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, "\t -b set benchmark json file\n");
+ fprintf(stderr, "\t -p set data source as pcap file\n");
+ fprintf(stderr, "\t -s set data source as json file\n");
+ fprintf(stderr, "\t -m set tcp Max Segment Size\n");
+ exit(1);
+}
+
+static int hdgt_parse_cmd_args(fake_stellar *fst, int argc, char **argv)
+{
+ int c, ret;
+
+ fst->tcp_mss = 1460; // set default value
+
+ while (1)
+ {
+ c = getopt_long(argc, argv, hdgt_cla_short_options, hdgt_cla_long_options, NULL);
+ if (c == -1)
+ {
+ ret = 0;
+ break;
+ }
+
+ switch (c)
+ {
+ case 'h':
+ hdgt_cmd_usage(argc, argv);
+ break;
+ case 'b':
+ fst->benchmark_json_file_name = optarg;
+ break;
+ case 'p':
+ fst->data_source_type = DATA_SOURCE_PCAP;
+ fst->data_source_file_name = optarg;
+ break;
+ case 's':
+ fst->data_source_type = DATA_SOURCE_JSON;
+ fst->data_source_file_name = optarg;
+ break;
+ case 'm':
+ fst->tcp_mss = atoi(optarg);
+ if (fst->tcp_mss <= 0 || fst->tcp_mss > 65535)
+ {
+ DEBUG_PRINT("Invalid tcp mss value! must be [1, 65535]\n");
+ hdgt_cmd_usage(argc, argv);
+ }
+ break;
+ default:
+ return -1;
+ break;
+ }
+ }
+
+ // check args
+ if (!fst->benchmark_json_file_name)
+ {
+ DEBUG_PRINT("benchmark json file is not set!\n");
+ hdgt_cmd_usage(argc, argv);
+ return -1;
+ }
+
+ if (__DATA_SOURCE_NULL == fst->data_source_type)
+ {
+ DEBUG_PRINT("data source is not set!\n");
+ hdgt_cmd_usage(argc, argv);
+ return -1;
+ }
+
+ return ret;
+}
+
+static int hdgt_compare_result(struct fake_stellar *fst)
+{
+ int final_result = 0;
+ cJSON_bool case_sensitive = FLASE;
+
+ if (!fst->http_plug_test_result_root)
+ {
+ DEBUG_PRINT("Not found test json result!\n");
+ return -1;
+ }
+
+ if (!fst->load_benchmark_json_root)
+ {
+ DEBUG_PRINT("Not found benchmark json instance!\n");
+ return -1;
+ }
+
+ if (cJSON_GetArraySize(fst->load_benchmark_json_root) != cJSON_GetArraySize(fst->http_plug_test_result_root))
+ {
+ DEBUG_PRINT("Compare json result: array size is diff!\n");
+ final_result++;
+ }
+
+ int ret = cJSON_Compare(fst->load_benchmark_json_root, fst->http_plug_test_result_root, case_sensitive);
+ if (ret != TRUE)
+ {
+ char *load_json_str = cJSON_Print(fst->load_benchmark_json_root);
+ printf("LOAD Raw:\n%s\n", load_json_str);
+
+ char *result_json_str = cJSON_Print(fst->http_plug_test_result_root);
+ printf("TEST Raw:\n%s\n", result_json_str);
+
+ int min_len = MIN(strlen(load_json_str), strlen(result_json_str));
+ for (size_t i = 0; i < min_len; i++)
+ {
+ if (load_json_str[i] != result_json_str[i])
+ {
+ printf("######### JSON Diff at len:%d: \n\tLOAD: %.*s\n\tTEST: %.*s\n", (int)i, 16, load_json_str + i, 16, result_json_str + i);
+ break;
+ }
+ }
+
+ free(load_json_str);
+ free(result_json_str);
+
+ cJSON *t_load = fst->load_benchmark_json_root->child;
+ cJSON *t_test = fst->http_plug_test_result_root->child;
+ while (t_load != NULL && t_test != NULL)
+ {
+ ret = cJSON_Compare(t_load, t_test, case_sensitive);
+ if (ret != TRUE)
+ {
+ load_json_str = cJSON_Print(t_load);
+ printf("LOAD Diff:\n%s\n", load_json_str);
+ free(load_json_str);
+ result_json_str = cJSON_Print(t_test);
+ printf("TEST Diff:\n%s\n", result_json_str);
+ free(result_json_str);
+ final_result++;
+ }
+ t_load = t_load->next;
+ t_test = t_test->next;
+ }
+ }
+ else
+ {
+ DEBUG_PRINT("Compare json result success!\n");
+ }
+
+ return final_result;
+}
+
+static char *hdgt_get_file_content(const char *filename)
+{
+ FILE *fp = fopen(filename, "r");
+ if (NULL == fp)
+ {
+ DEBUG_PRINT("fopen() fail!\n");
+ return NULL;
+ }
+ fseek(fp, 0, SEEK_END);
+ long file_size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ char *file_content = MMALLOC(char, file_size + 1);
+ if (fread(file_content, 1, file_size, fp) != file_size)
+ {
+ DEBUG_PRINT("fread() %s fail!\n", filename);
+ fclose(fp);
+ MFREE(file_content);
+ return NULL;
+ }
+ fclose(fp);
+ return file_content;
+}
+
+static struct fake_session *hdgt_session_new(struct fake_stellar *fst)
+{
+ struct fake_session *fses = MMALLOC(struct fake_session, sizeof(struct fake_session));
+
+ fses->fst = fst;
+ fses->tcp_mss = fst->tcp_mss;
+ // todo : get protocol type from packet, not fixed
+ fses->type = SESSION_TYPE_TCP;
+ return fses;
+}
+
+static int hdgt_get_packet_from_pcap(struct fake_stellar *fst, struct fake_packet *fpkt)
+{
+ struct pcap_pkthdr *pkt_hdr;
+ const u_char *pkt_data;
+ int ret = pcap_next_ex(fst->pcap_ins, &pkt_hdr, &pkt_data);
+ if (1 == ret)
+ {
+ fpkt->raw_pkt_data = (char *)pkt_data;
+ fpkt->raw_pkt_data_len = pkt_hdr->caplen;
+ DEBUG_PRINT("Warning! this is a rough packet decoder, not support tcp out of order, seq overlap...\n");
+ return 1;
+ }
+ else if (-2 == ret)
+ {
+ DEBUG_PRINT("pcap file over!\n");
+ return 0;
+ }
+ DEBUG_PRINT("pcap_next_ex() fail: %s!\n", pcap_geterr(fst->pcap_ins));
+ return -1;
+}
+
+static int hdgt_get_packet_from_json(struct fake_stellar *fst, struct fake_packet *fpkt)
+{
+ cJSON *json_root = fst->data_src_json_para.json_root;
+ cJSON *json_item = cJSON_GetArrayItem(json_root, fst->data_src_json_para.current_json_array_idx);
+ if (NULL == json_item)
+ {
+ DEBUG_PRINT("Not found json object at index %d!\n", fst->data_src_json_para.current_json_array_idx);
+ return -1;
+ }
+ fst->data_src_json_para.current_object = json_item;
+ fst->data_src_json_para.current_json_array_idx++;
+ return 1;
+}
+
+static int hdgt_is_reserverd_json_header(const cJSON *json_object)
+{
+ for (size_t i = 0; g_data_src_json_non_headers[i] != NULL; i++)
+ {
+ if (strlen(json_object->string) == strlen(g_data_src_json_non_headers[i]) && (0 == strncmp(json_object->string, g_data_src_json_non_headers[i], strlen(g_data_src_json_non_headers[i]))))
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int hdgt_get_packet_dir_from_json(cJSON *json)
+{
+ cJSON *json_dir = cJSON_GetObjectItem(json, GTEST_HTTP_TRANS_NAME);
+ if (NULL == json_dir)
+ {
+ return -1;
+ }
+ if (strncasecmp("request", json_dir->valuestring, strlen("request")) == 0)
+ {
+ return PACKET_DIRECTION_C2S;
+ }
+ else if (strncasecmp("response", json_dir->valuestring, strlen("response")) == 0)
+ {
+ return PACKET_DIRECTION_S2C;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+static void hdgt_get_req_line_from_json(struct data_src_json_para_t *data_src_json_para)
+{
+ cJSON *json_item = data_src_json_para->current_object;
+ cJSON *method, *uri, *major_version, *minor_version;
+
+ method = cJSON_GetObjectItem(json_item, "method");
+ uri = cJSON_GetObjectItem(json_item, "uri");
+ major_version = cJSON_GetObjectItem(json_item, "major_version");
+ minor_version = cJSON_GetObjectItem(json_item, "minor_version");
+
+ if (method && uri && major_version && minor_version)
+ {
+ sprintf(data_src_json_para->key_value_buf, "%s %s HTTP/%d.%d\r\n",
+ method->valuestring, uri->valuestring, major_version->valueint, minor_version->valueint);
+ }
+ else
+ {
+ DEBUG_PRINT("get request line from json fail!\n");
+ }
+}
+
+static void hdgt_get_res_line_from_json(struct data_src_json_para_t *data_src_json_para)
+{
+ cJSON *json_item = data_src_json_para->current_object;
+ cJSON *res_status, *status_code, *major_version, *minor_version;
+
+ res_status = cJSON_GetObjectItem(json_item, "res_status");
+ status_code = cJSON_GetObjectItem(json_item, "status_code");
+ major_version = cJSON_GetObjectItem(json_item, "major_version");
+ minor_version = cJSON_GetObjectItem(json_item, "minor_version");
+
+ if (res_status && status_code && major_version && minor_version)
+ {
+ sprintf(data_src_json_para->key_value_buf, "HTTP/%d.%d %d %s\r\n",
+ major_version->valueint, minor_version->valueint, status_code->valueint, res_status->valuestring);
+ }
+ else
+ {
+ DEBUG_PRINT("get response line from json fail!\n");
+ }
+}
+
+static void hdgt_get_headers_from_json(struct data_src_json_para_t *data_src_json_para)
+{
+ cJSON *json_item = data_src_json_para->current_object->child;
+ char *data_ptr = data_src_json_para->key_value_buf + strlen(data_src_json_para->key_value_buf);
+ int len;
+
+ while (json_item)
+ {
+ if (0 == hdgt_is_reserverd_json_header(json_item))
+ {
+ if (cJSON_IsString(json_item))
+ {
+ len = sprintf(data_ptr, "%s: %s\r\n", json_item->string, json_item->valuestring);
+ }
+ else if (cJSON_IsNumber(json_item))
+ {
+ len = sprintf(data_ptr, "%s: %d\r\n", json_item->string, json_item->valueint);
+ }
+ else if (cJSON_IsBool(json_item))
+ {
+ len = sprintf(data_ptr, "%s: %s\r\n", json_item->string, json_item->valueint ? "true" : "false");
+ }
+ else
+ {
+ len = sprintf(data_ptr, "%s: %s\r\n", json_item->string, cJSON_Print(json_item));
+ }
+ data_ptr += len;
+ }
+ json_item = json_item->next;
+ }
+ sprintf(data_ptr, "\r\n"); // headers EOF
+}
+
+static int hdgt_update_packet_detail_by_json(struct fake_stellar *fst, struct fake_packet *fpkt)
+{
+ fpkt->dir = hdgt_get_packet_dir_from_json(fst->data_src_json_para.current_object);
+ if (PACKET_DIRECTION_C2S != fpkt->dir && PACKET_DIRECTION_S2C != fpkt->dir)
+ {
+ DEBUG_PRINT("get packet direction from json fail!\n");
+ return -1;
+ }
+
+ if (PACKET_DIRECTION_C2S == fpkt->dir)
+ {
+ hdgt_get_req_line_from_json(&fst->data_src_json_para);
+ }
+ else
+ {
+ hdgt_get_res_line_from_json(&fst->data_src_json_para);
+ }
+ hdgt_get_headers_from_json(&fst->data_src_json_para);
+
+ fpkt->payload_data = fst->data_src_json_para.key_value_buf;
+ fpkt->payload_data_len = strlen(fst->data_src_json_para.key_value_buf);
+ fpkt->payload_submit_offset = 0;
+
+ return 0;
+}
+
+static int hdgt_get_packet(struct fake_stellar *fst, struct fake_packet *fpkt)
+{
+ int ret = -1;
+ memset(fpkt, 0, sizeof(struct fake_packet));
+ if (DATA_SOURCE_PCAP == fst->data_source_type)
+ {
+ ret = hdgt_get_packet_from_pcap(fst, fpkt);
+ }
+ else if (DATA_SOURCE_JSON == fst->data_source_type)
+ {
+ ret = hdgt_get_packet_from_json(fst, fpkt);
+ }
+ else
+ {
+ DEBUG_PRINT("Invalid data source type!\n");
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static int hdgt_has_data_left(struct fake_packet *fpkt)
+{
+ return fpkt->payload_submit_offset < fpkt->payload_data_len;
+}
+
+static int hdgt_get_packet_dir_of_tcp(const struct tcphdr *th)
+{
+ int dir;
+
+ if (TH_SYN == th->th_flags)
+ {
+ dir = PACKET_DIRECTION_C2S;
+ }
+ else if ((TH_SYN | TH_ACK) == th->th_flags)
+ {
+ dir = PACKET_DIRECTION_S2C;
+ }
+ else
+ {
+ if (ntohs(th->th_sport) > ntohs(th->th_dport)) // Unwritten rule, not sure
+ {
+ dir = PACKET_DIRECTION_C2S;
+ }
+ else
+ {
+ dir = PACKET_DIRECTION_S2C;
+ }
+ }
+
+ return dir;
+}
+
+static void hdgt_determine_packet_dir(struct fake_session *fses, struct fake_packet *fpkt, const struct tcphdr *th)
+{
+ if (SESSION_ADDR_TYPE_IPV4_TCP == fses->addr_type)
+ {
+ if (th->th_sport == fses->addr->ipv4.sport)
+ {
+ fpkt->dir = PACKET_DIRECTION_C2S;
+ }
+ else
+ {
+ fpkt->dir = PACKET_DIRECTION_S2C;
+ }
+ }
+ else
+ {
+ if (th->th_sport == fses->addr->ipv6.sport)
+ {
+ fpkt->dir = PACKET_DIRECTION_C2S;
+ }
+ else
+ {
+ fpkt->dir = PACKET_DIRECTION_S2C;
+ }
+ }
+}
+
+static void hdgt_update_packet_payload(struct fake_packet *fpkt, const struct tcphdr *th)
+{
+ // todo, support UDP?
+ fpkt->payload_data = (char *)th + th->th_off * 4;
+ fpkt->payload_data_len = fpkt->raw_pkt_data_len - ((char *)th - fpkt->raw_pkt_data) - (th->th_off * 4);
+ fpkt->payload_submit_offset = 0;
+}
+
+static int hdgt_update_packet_detail_by_pcap(struct fake_session *fses, struct fake_packet *fpkt)
+{
+ struct tcphdr *th = (struct tcphdr *)MESA_jump_layer_greedy(fpkt->raw_pkt_data, ADDR_TYPE_MAC, ADDR_TYPE_TCP);
+ if (NULL == th)
+ {
+ DEBUG_PRINT("Not found tcp header!\n");
+ return -1;
+ }
+
+ hdgt_determine_packet_dir(fses, fpkt, th);
+ hdgt_update_packet_payload(fpkt, th);
+ return 0;
+}
+
+static int hdgt_update_packet_detail(struct fake_session *fses, struct fake_packet *fpkt)
+{
+ int ret;
+ if (DATA_SOURCE_PCAP == fses->fst->data_source_type)
+ {
+ ret = hdgt_update_packet_detail_by_pcap(fses, fpkt);
+ }
+ else
+ {
+ ret = hdgt_update_packet_detail_by_json(fses->fst, fpkt);
+ }
+ return ret;
+}
+
+static struct session_addr *hgdt_get_session_addr_by_pcap(struct fake_session *fses, struct fake_packet *fpkt)
+{
+ if (fses->addr != NULL)
+ {
+ return fses->addr;
+ }
+
+ struct session_addr tmp_addr = {};
+ int innermost_l3_layer_type;
+
+ struct tcphdr *th = (struct tcphdr *)MESA_jump_layer_greedy(fpkt->raw_pkt_data, ADDR_TYPE_MAC, ADDR_TYPE_TCP);
+ if (NULL == th)
+ {
+ DEBUG_PRINT("Not found tcp header!\n");
+ // todo, support UDP ?
+ return NULL;
+ }
+ fpkt->l4_header = (char *)th;
+ struct ip *i4h = (struct ip *)MESA_jump_layer_greedy(fpkt->raw_pkt_data, ADDR_TYPE_MAC, ADDR_TYPE_IPV4);
+ struct ip6_hdr *i6h = (struct ip6_hdr *)MESA_jump_layer_greedy(fpkt->raw_pkt_data, ADDR_TYPE_MAC, ADDR_TYPE_IPV6);
+ if ((NULL == i6h) && (NULL == i4h))
+ {
+ DEBUG_PRINT("Not found ipv4 or ipv6 header!\n");
+ return NULL;
+ }
+
+ if (i4h && (NULL == i6h))
+ {
+ innermost_l3_layer_type = ADDR_TYPE_IPV4;
+ }
+ else if (i6h && (NULL == i4h))
+ {
+ innermost_l3_layer_type = ADDR_TYPE_IPV6;
+ }
+ else // both ipv4 and ipv6 exist, maybe 4over6, 6over4, gtp etc.
+ {
+ if ((char *)i4h - (char *)i6h > 0)
+ {
+ innermost_l3_layer_type = ADDR_TYPE_IPV4;
+ }
+ else
+ {
+ innermost_l3_layer_type = ADDR_TYPE_IPV6;
+ }
+ }
+
+ int cur_pkt_dir = hdgt_get_packet_dir_of_tcp(th);
+
+ if (ADDR_TYPE_IPV4 == innermost_l3_layer_type)
+ {
+ fses->addr_type = SESSION_ADDR_TYPE_IPV4_TCP;
+ if (PACKET_DIRECTION_C2S == cur_pkt_dir)
+ {
+
+ tmp_addr.ipv4.saddr = i4h->ip_src.s_addr;
+ tmp_addr.ipv4.daddr = i4h->ip_dst.s_addr;
+ tmp_addr.ipv4.sport = th->th_sport;
+ tmp_addr.ipv4.dport = th->th_dport;
+ }
+ else
+ {
+ tmp_addr.ipv4.saddr = i4h->ip_dst.s_addr;
+ tmp_addr.ipv4.daddr = i4h->ip_src.s_addr;
+ tmp_addr.ipv4.sport = th->th_dport;
+ tmp_addr.ipv4.dport = th->th_sport;
+ }
+ }
+ else
+ {
+ fses->addr_type = SESSION_ADDR_TYPE_IPV6_TCP;
+ if (PACKET_DIRECTION_C2S == cur_pkt_dir)
+ {
+ memcpy(tmp_addr.ipv6.saddr, i6h->ip6_src.s6_addr, IPV6_ADDR_LEN);
+ memcpy(tmp_addr.ipv6.daddr, i6h->ip6_dst.s6_addr, IPV6_ADDR_LEN);
+ tmp_addr.ipv6.sport = th->th_sport;
+ tmp_addr.ipv6.dport = th->th_dport;
+ }
+ else
+ {
+ memcpy(tmp_addr.ipv6.saddr, i6h->ip6_dst.s6_addr, IPV6_ADDR_LEN);
+ memcpy(tmp_addr.ipv6.daddr, i6h->ip6_src.s6_addr, IPV6_ADDR_LEN);
+ tmp_addr.ipv6.sport = th->th_dport;
+ tmp_addr.ipv6.dport = th->th_sport;
+ }
+ }
+
+ fses->addr = MMALLOC(struct session_addr, sizeof(struct session_addr));
+ memcpy(fses->addr, &tmp_addr, sizeof(struct session_addr));
+
+ return fses->addr;
+}
+
+/* example: "1.1.1.1.12345>2.2.2.2.80" */
+static void hdgt_json_session_adddr_pton_v4(struct session_addr *addr, const char *tuple4_cstr)
+{
+ const char *delim = ".>";
+ char *dup_str = strdup(tuple4_cstr);
+ char *sip1 = strtok(dup_str, delim);
+ char *sip2 = strtok(NULL, delim);
+ char *sip3 = strtok(NULL, delim);
+ char *sip4 = strtok(NULL, delim);
+ char *sport = strtok(NULL, delim);
+
+ char *dip1 = strtok(NULL, delim);
+ char *dip2 = strtok(NULL, delim);
+ char *dip3 = strtok(NULL, delim);
+ char *dip4 = strtok(NULL, delim);
+ char *dport = strtok(NULL, delim);
+
+ char sip_str[INET_ADDRSTRLEN] = {};
+ char dip_str[INET_ADDRSTRLEN] = {};
+ sprintf(sip_str, "%s.%s.%s.%s", sip1, sip2, sip3, sip4);
+ inet_pton(AF_INET, sip_str, &addr->ipv4.saddr);
+ sprintf(dip_str, "%s.%s.%s.%s", dip1, dip2, dip3, dip4);
+ inet_pton(AF_INET, dip_str, &addr->ipv4.daddr);
+ addr->ipv4.sport = htons(atoi(sport));
+ addr->ipv4.dport = htons(atoi(dport));
+}
+
+/* fe80::8c19:7aff:fef2:11e5.12345>fe80::8c19:7aff:fef2:11e5.80 */
+static void hdgt_json_session_adddr_pton_v6(struct session_addr *addr, const char *tuple4_cstr)
+{
+ char *dup_str = strdup(tuple4_cstr);
+ char *sip = dup_str;
+ char *ptr = strchr(dup_str, '.');
+ *ptr = '\0';
+ ptr++;
+ char *sport = ptr;
+ ptr = strchr(sport, '>');
+ *ptr = '\0';
+ ptr++;
+ char *dip = ptr;
+ ptr = strchr(dip, '.');
+ *ptr = '\0';
+ ptr++;
+ char *dport = ptr;
+
+ char sip_str[INET6_ADDRSTRLEN] = {};
+ char dip_str[INET6_ADDRSTRLEN] = {};
+ inet_pton(AF_INET6, sip, addr->ipv6.saddr);
+ inet_pton(AF_INET6, dip, addr->ipv6.daddr);
+ addr->ipv6.sport = htons(atoi(sport));
+ addr->ipv6.dport = htons(atoi(dport));
+}
+
+static void hdgt_json_session_adddr_pton(struct fake_session *fses, const char *tuple4_cstr)
+{
+ struct session_addr *addr = fses->addr;
+ if (strchr(tuple4_cstr, ':') != NULL)
+ {
+ hdgt_json_session_adddr_pton_v6(addr, tuple4_cstr);
+ fses->addr_type = SESSION_ADDR_TYPE_IPV6_TCP;
+ return;
+ }
+ else
+ {
+ hdgt_json_session_adddr_pton_v4(addr, tuple4_cstr);
+ fses->addr_type = SESSION_ADDR_TYPE_IPV4_TCP;
+ }
+}
+
+static struct session_addr *hgdt_get_session_addr_by_json(struct fake_session *fses, struct fake_packet *fpkt)
+{
+ if (fses->addr != NULL)
+ {
+ return fses->addr;
+ }
+
+ cJSON *tuple4_obj = cJSON_GetObjectItem(fses->fst->data_src_json_para.current_object, GTEST_HTTP_TUPLE4_NAME);
+ if (NULL == tuple4_obj)
+ {
+ return NULL;
+ }
+ fses->addr = MMALLOC(struct session_addr, sizeof(struct session_addr));
+ hdgt_json_session_adddr_pton(fses, tuple4_obj->valuestring);
+ return fses->addr;
+}
+
+static struct session_addr *hgdt_get_session_addr(struct fake_session *fses, struct fake_packet *fpkt)
+{
+ if (fses->fst->data_source_type == DATA_SOURCE_PCAP)
+ {
+ return hgdt_get_session_addr_by_pcap(fses, fpkt);
+ }
+ return hgdt_get_session_addr_by_json(fses, fpkt);
+}
+
+static void hdgt_session_update(struct fake_stellar *fst, struct fake_session *fses, int event)
+{
+ fst->http_decoder_entry((struct session *)fses, event, (struct packet *)fses->fpkt, fst->http_decoder_ctx);
+}
+
+static int hdgt_data_source_init(struct fake_stellar *fst)
+{
+ if (DATA_SOURCE_PCAP == fst->data_source_type)
+ {
+ char errbuf[PCAP_ERRBUF_SIZE];
+ fst->pcap_ins = pcap_open_offline(fst->data_source_file_name, errbuf);
+ if (NULL == fst->pcap_ins)
+ {
+ fprintf(stderr, "pcap_open_offline() fail: %s\n", errbuf);
+ return -1;
+ }
+ }
+ else if (DATA_SOURCE_JSON == fst->data_source_type)
+ {
+ char *file_cont = hdgt_get_file_content(fst->data_source_file_name);
+ if (NULL == file_cont)
+ {
+ fprintf(stderr, "Open json file fail: %s\n", fst->data_source_file_name);
+ return -1;
+ }
+ fst->data_src_json_para.json_root = cJSON_Parse(file_cont);
+ if (NULL == fst->data_src_json_para.json_root)
+ {
+ fprintf(stderr, "cJSON_Parse() %s fail!\n", fst->data_source_file_name);
+ MFREE(file_cont);
+ return -1;
+ }
+ fst->data_src_json_para.json_array_size = cJSON_GetArraySize(fst->data_src_json_para.json_root);
+ fst->data_src_json_para.current_json_array_idx = 0;
+ MFREE(file_cont);
+ }
+ else
+ {
+ DEBUG_PRINT("Invalid data source type!\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int hdgt_benchmakr_json_parse(struct fake_stellar *fst)
+{
+ char *file_cont = hdgt_get_file_content(fst->benchmark_json_file_name);
+ if (NULL == file_cont)
+ {
+ fprintf(stderr, "Open json file fail: %s\n", fst->benchmark_json_file_name);
+ return -1;
+ }
+ fst->load_benchmark_json_root = cJSON_Parse(file_cont);
+ if (NULL == fst->load_benchmark_json_root)
+ {
+ fprintf(stderr, "cJSON_Parse() %s fail!\n", fst->benchmark_json_file_name);
+ MFREE(file_cont);
+ return -1;
+ }
+ fst->http_plug_test_result_root = cJSON_CreateArray();
+ MFREE(file_cont);
+ return 0;
+}
+
+static int hdgt_under_test_module_init(struct fake_stellar *fst)
+{
+ fst->http_decoder_ctx = http_decoder_init((struct stellar *)fst);
+ fst->http_decoder_entry = http_decoder_entry;
+
+ return 0;
+}
+
+static int hdgt_test_plug_init(struct fake_stellar *fst)
+{
+ fst->http_http_plug_ctx = http_decoder_test_init((struct stellar *)fst);
+ return 0;
+}
+
+static struct fake_stellar *hdgt_init(int argc, char **argv)
+{
+ struct fake_stellar *fst = MMALLOC(struct fake_stellar, sizeof(struct fake_stellar));
+
+ if (hdgt_parse_cmd_args(fst, argc, argv) < 0)
+ {
+ fprintf(stderr, "hdgt_parse_cmd_args() fail!\n");
+ goto fail_exit;
+ }
+ if (hdgt_data_source_init(fst) < 0)
+ {
+ fprintf(stderr, "hdgt_data_source_init() fail!\n");
+ goto fail_exit;
+ }
+ if (hdgt_benchmakr_json_parse(fst) < 0)
+ {
+ fprintf(stderr, "hdgt_benchmakr_json_parse() fail!\n");
+ goto fail_exit;
+ }
+ if (hdgt_under_test_module_init(fst) < 0)
+ {
+ fprintf(stderr, "hdgt_under_test_module_init() fail!\n");
+ goto fail_exit;
+ }
+ if (hdgt_test_plug_init(fst) < 0)
+ {
+ fprintf(stderr, "hdgt_test_plug_init() fail!\n");
+ goto fail_exit;
+ }
+
+ g_fake_stellar = fst;
+ return fst;
+
+fail_exit:
+ MFREE(fst);
+ return NULL;
+}
+
+static void hdgt_exit(struct fake_stellar *fst)
+{
+ cJSON_free(fst->load_benchmark_json_root);
+ cJSON_free(fst->http_plug_test_result_root);
+ if (fst->data_src_json_para.json_root)
+ {
+ cJSON_free(fst->data_src_json_para.json_root);
+ }
+ if (fst->pcap_ins)
+ {
+ pcap_close(fst->pcap_ins);
+ }
+
+ MFREE(fst);
+ return;
+}
+
+static void hdgt_session_free(struct fake_session *fses)
+{
+ const struct fake_exdata_manage *ex_mgr = fses->fst->fake_exdata_mgr;
+
+ for (int i = 0; i < EX_DATA_MAX_SIZE; i++)
+ {
+ if (fses->plug_exdata_array[i] != NULL)
+ {
+ ex_mgr[i].free_func((struct session *)fses, i, fses->plug_exdata_array[i], ex_mgr[i].arg);
+ }
+ }
+
+ if (fses->readable_addr_cstr)
+ {
+ MFREE(fses->readable_addr_cstr);
+ }
+
+ if (fses->addr)
+ {
+ MFREE(fses->addr);
+ }
+
+ MFREE(fses);
+}
+
+static void hdgt_main_loop(struct fake_stellar *fst)
+{
+ struct fake_packet __null_pkt = {};
+ struct fake_packet fpkt = {};
+ struct fake_session *fses = hdgt_session_new(fst);
+
+ fses->fpkt = &__null_pkt;
+ hdgt_session_update(fst, fses, SESS_EV_OPENING);
+
+ fses->fpkt = &fpkt;
+ while (hdgt_get_packet(fst, &fpkt) == 1)
+ {
+ if (NULL == hgdt_get_session_addr(fses, &fpkt))
+ {
+ continue; // not tcp, not ip
+ }
+ if (hdgt_update_packet_detail(fses, &fpkt) < 0)
+ {
+ continue;
+ }
+ while (hdgt_has_data_left(&fpkt))
+ {
+ hdgt_session_update(fst, fses, SESS_EV_PACKET);
+ }
+ }
+
+ fses->fpkt = &__null_pkt;
+ hdgt_session_update(fst, fses, SESS_EV_CLOSING);
+
+ hdgt_session_free(fses);
+}
+
+TEST(HTTP_DECODER, GTEST)
+{
+ ASSERT_EQ(0, hdgt_compare_result(g_fake_stellar));
+}
+
+int main(int argc, char **argv)
+{
+ struct fake_stellar *fake_st = hdgt_init(argc, argv);
+ if (NULL == fake_st)
+ {
+ fprintf(stderr, "hdgt_init() fail!\n");
+ exit(1);
+ }
+
+ hdgt_main_loop(fake_st);
+
+ ::testing::InitGoogleTest(&argc, argv);
+ int ret = RUN_ALL_TESTS();
+
+ hdgt_exit(fake_st);
+
+ return ret;
+}
diff --git a/test/http_decoder_gtest.cpp b/test/http_decoder_gtest.cpp
new file mode 100644
index 0000000..aa81df2
--- /dev/null
+++ b/test/http_decoder_gtest.cpp
@@ -0,0 +1,370 @@
+/*
+**********************************************************************************************
+* File: http_decoder_gtest.cpp
+* Description:
+* Authors: Liu WenTan <[email protected]>
+* Date: 2023-12-15
+* Copyright: (c) Since 2023 Geedge Networks, Ltd. All rights reserved.
+***********************************************************************************************
+*/
+
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+
+#include "../include/http_decoder.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+
+#include "cJSON.h"
+#include "http_decoder_gtest.h"
+#include "stellar/utils.h"
+#include "stellar/stellar.h"
+#include "stellar/session_exdata.h"
+#include "stellar/session_mq.h"
+
+ int commit_test_result_json(cJSON *node, const char *name);
+}
+#endif
+
+#define MAX_KEY_STR_LEN 2048
+
+enum http_transaction_type
+{
+ HTTP_TRANSACTION_REQ = 0,
+ HTTP_TRANSACTION_RES,
+ HTTP_TRANSACTION_SESSION,
+ HTTP_TRANSACTION_MAX
+};
+
+struct gtest_plug_exdata_t
+{
+ cJSON *result_jnode[HTTP_TRANSACTION_MAX];
+};
+
+static int g_result_count = 0;
+static int g_header_count = 1;
+static int g_exdata_idx = 0;
+static int g_topic_id = 0;
+
+#if 1
+void output_http_req_line(struct http_request_line *req_line)
+{
+ char tmp_str[MAX_KEY_STR_LEN] = {0};
+ memcpy(tmp_str, req_line->method.str, req_line->method.str_len);
+ printf("req_method:%s\n", tmp_str);
+
+ memset(tmp_str, 0, sizeof(tmp_str));
+ memcpy(tmp_str, req_line->uri.str, req_line->uri.str_len);
+ printf("req_uri:%s\n", tmp_str);
+
+ memset(tmp_str, 0, sizeof(tmp_str));
+ memcpy(tmp_str, req_line->version.str, req_line->version.str_len);
+ printf("req_version:%s\n", tmp_str);
+}
+
+void output_http_res_line(struct http_response_line *res_line)
+{
+ char tmp_str[MAX_KEY_STR_LEN] = {0};
+ memcpy(tmp_str, res_line->version.str, res_line->version.str_len);
+ printf("res_version:%s\n", tmp_str);
+
+ memset(tmp_str, 0, sizeof(tmp_str));
+ memcpy(tmp_str, res_line->status.str, res_line->status.str_len);
+ printf("res_status:%s\n", tmp_str);
+}
+
+void output_http_header(struct http_header *header)
+{
+ char tmp_key[MAX_KEY_STR_LEN] = {0};
+ char tmp_val[MAX_KEY_STR_LEN] = {0};
+
+ memcpy(tmp_key, header->key.str, header->key.str_len);
+ memcpy(tmp_val, header->val.str, header->val.str_len);
+ printf("<%s:%s>\n", tmp_key, tmp_val);
+}
+#endif
+
+void output_http_body(struct hstring *body, int decompress_flag)
+{
+ int counter = 0;
+
+ if (1 == decompress_flag)
+ {
+ printf("\n\n----------------decompress body len:%zu---------------\n",
+ body->str_len);
+ }
+ else
+ {
+ printf("\n\n----------------raw body len:%zu---------------\n",
+ body->str_len);
+ }
+
+ for (size_t i = 0; i < body->str_len; i++)
+ {
+ if (counter % 16 == 0)
+ {
+ printf("\n");
+ }
+ printf("%02x ", (unsigned char)body->str[i]);
+ counter++;
+ }
+ printf("\n");
+}
+
+int http_field_to_json(cJSON *object, const char *key, char *val, size_t val_len)
+{
+ if (NULL == object || NULL == key || NULL == val || 0 == val_len)
+ {
+ return -1;
+ }
+
+ char *tmp = CALLOC(char, val_len + 1);
+ memcpy(tmp, val, val_len);
+ cJSON_AddStringToObject(object, key, tmp);
+ FREE(tmp);
+
+ return 0;
+}
+
+void req_line_to_json(cJSON *ctx, struct http_request_line *req_line)
+{
+ http_field_to_json(ctx, "method", req_line->method.str,
+ req_line->method.str_len);
+ http_field_to_json(ctx, "uri", req_line->uri.str, req_line->uri.str_len);
+ http_field_to_json(ctx, "req_version", req_line->version.str,
+ req_line->version.str_len);
+
+ cJSON_AddNumberToObject(ctx, "major_version", req_line->major_version);
+ cJSON_AddNumberToObject(ctx, "minor_version", req_line->minor_version);
+}
+
+void res_line_to_json(cJSON *ctx, struct http_response_line *res_line)
+{
+ http_field_to_json(ctx, "res_version", res_line->version.str,
+ res_line->version.str_len);
+ http_field_to_json(ctx, "res_status", res_line->status.str,
+ res_line->status.str_len);
+
+ cJSON_AddNumberToObject(ctx, "major_version", res_line->major_version);
+ cJSON_AddNumberToObject(ctx, "minor_version", res_line->minor_version);
+ cJSON_AddNumberToObject(ctx, "status_code", res_line->status_code);
+}
+
+void http_header_to_json(cJSON *ctx, struct http_header *header)
+{
+ char key[MAX_KEY_STR_LEN] = {0};
+
+ memcpy(key, header->key.str, header->key.str_len);
+
+ if (cJSON_HasObjectItem(ctx, key) == FALSE)
+ {
+ http_field_to_json(ctx, key, header->val.str, header->val.str_len);
+ }
+ else
+ {
+ // ctx already has the key, so rename key by key%d
+ char new_key[MAX_KEY_STR_LEN] = {0};
+ sprintf(new_key, "%s%d", key, g_header_count++);
+ http_field_to_json(ctx, new_key, header->val.str, header->val.str_len);
+ }
+}
+
+void http_url_add_to_json(cJSON *ctx, struct http_message *msg)
+{
+ struct hstring url_result = {};
+
+ if (cJSON_GetObjectItem(ctx, GTEST_HTTP_URL_NAME))
+ {
+ return;
+ }
+
+ if (http_message_get_url(msg, &url_result) < 0)
+ {
+ // printf("url:%s\n", url_result.str);
+ return;
+ }
+
+ struct http_header url_header_result = {};
+
+ url_header_result.key.str = (char *)GTEST_HTTP_URL_NAME;
+ url_header_result.key.str_len = strlen(GTEST_HTTP_URL_NAME);
+ url_header_result.val = url_result;
+
+ http_header_to_json(ctx, &url_header_result);
+}
+
+// Full duplex
+static void commit_last_half_flow_data(struct session *sess, struct gtest_plug_exdata_t *gtest_plug_exdata, enum http_transaction_type type)
+{
+ char result_name[MAX_KEY_STR_LEN] = {0};
+
+ cJSON *last_jnode = gtest_plug_exdata->result_jnode[type];
+ if (last_jnode)
+ {
+ sprintf(result_name, "%d", g_result_count);
+ commit_test_result_json(last_jnode, result_name);
+ gtest_plug_exdata->result_jnode[type] = NULL;
+ g_result_count++;
+ }
+
+ gtest_plug_exdata->result_jnode[type] = cJSON_CreateObject();
+ if (HTTP_TRANSACTION_REQ == type)
+ {
+ cJSON_AddStringToObject(gtest_plug_exdata->result_jnode[type], GTEST_HTTP_TRANS_NAME, "request");
+ }
+ else if (HTTP_TRANSACTION_RES == type)
+ {
+ cJSON_AddStringToObject(gtest_plug_exdata->result_jnode[type], GTEST_HTTP_TRANS_NAME, "response");
+ }
+}
+
+static void http_decoder_test_update_session_tuple4(struct session *sess, struct gtest_plug_exdata_t *gtest_plug_exdata)
+{
+ if (gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_SESSION] == NULL)
+ {
+ char result_name[MAX_KEY_STR_LEN] = {0};
+ gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_SESSION] = cJSON_CreateObject();
+ cJSON_AddStringToObject(gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_SESSION], GTEST_HTTP_TUPLE4_NAME, session_get0_readable_addr(sess));
+ sprintf(result_name, "%d", g_result_count++);
+ commit_test_result_json(gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_SESSION], result_name);
+ }
+}
+
+extern "C" int
+http_decoder_test_entry(struct session *sess, int topic_id, const void *data,
+ void *cb_arg)
+{
+ struct http_request_line req_line = {0};
+ struct http_response_line res_line = {0};
+ struct http_header header = {0};
+ struct hstring body = {0};
+ struct http_message *msg = (struct http_message *)data;
+ enum http_message_type msg_type = http_message_type(msg);
+
+ struct gtest_plug_exdata_t *gtest_plug_exdata = (struct gtest_plug_exdata_t *)session_get_ex_data(sess, g_exdata_idx);
+ if (NULL == gtest_plug_exdata)
+ {
+ gtest_plug_exdata = (struct gtest_plug_exdata_t *)calloc(1, sizeof(struct gtest_plug_exdata_t));
+ session_set_ex_data(sess, g_exdata_idx, gtest_plug_exdata);
+ }
+
+ if (msg_type == HTTP_MESSAGE_REQ_LINE || msg_type == HTTP_MESSAGE_REQ_HEADER || msg_type == HTTP_MESSAGE_REQ_BODY)
+ {
+ cJSON *json = gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_REQ];
+ }
+ else
+ {
+ cJSON *json = gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_RES];
+ }
+
+ http_decoder_test_update_session_tuple4(sess, gtest_plug_exdata);
+
+ switch (msg_type)
+ {
+ case HTTP_MESSAGE_REQ_LINE:
+ commit_last_half_flow_data(sess, gtest_plug_exdata, HTTP_TRANSACTION_REQ);
+ http_message_get_request_line(msg, &req_line);
+ req_line_to_json(gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_REQ], &req_line);
+ break;
+ case HTTP_MESSAGE_REQ_HEADER:
+ while (http_message_request_header_next(msg, &header) > 0)
+ {
+ http_header_to_json(gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_REQ], &header);
+ }
+ g_header_count = 1;
+ http_url_add_to_json(gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_REQ], msg);
+ break;
+ case HTTP_MESSAGE_REQ_BODY:
+ http_message_get_request_raw_body(msg, &body);
+ // output_http_body(&body, 0);
+
+ http_message_get_request_decompress_body(msg, &body);
+ // output_http_body(&body, 1);
+ break;
+ case HTTP_MESSAGE_RES_LINE:
+ commit_last_half_flow_data(sess, gtest_plug_exdata, HTTP_TRANSACTION_RES);
+ http_message_get_response_line(msg, &res_line);
+ res_line_to_json(gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_RES], &res_line);
+ break;
+ case HTTP_MESSAGE_RES_HEADER:
+ while (http_message_response_header_next(msg, &header) > 0)
+ {
+ http_header_to_json(gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_RES], &header);
+ }
+ g_header_count = 1;
+ break;
+ case HTTP_MESSAGE_RES_BODY:
+ http_message_get_response_raw_body(msg, &body);
+ // output_http_body(&body, 0);
+
+ http_message_get_response_decompress_body(msg, &body);
+ // output_http_body(&body, 1);
+ break;
+
+ // to do: check payload
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void http_decoder_test_exdata_free(struct session *sess, int idx, void *ex_ptr,
+ void *arg)
+{
+ if (ex_ptr != NULL)
+ {
+ struct gtest_plug_exdata_t *gtest_plug_exdata = (struct gtest_plug_exdata_t *)ex_ptr;
+ if (gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_REQ])
+ {
+ commit_last_half_flow_data(sess, gtest_plug_exdata, HTTP_TRANSACTION_REQ);
+ }
+ if (gtest_plug_exdata->result_jnode[HTTP_TRANSACTION_RES])
+ {
+ commit_last_half_flow_data(sess, gtest_plug_exdata, HTTP_TRANSACTION_RES);
+ }
+ free(ex_ptr);
+ }
+}
+
+extern "C" void *http_decoder_test_init(struct stellar *st)
+{
+ g_exdata_idx =
+ stellar_session_get_ex_new_index(st, "HTTP_DECODER_REQ_TEST",
+ http_decoder_test_exdata_free,
+ NULL);
+ if (g_exdata_idx < 0)
+ {
+ printf("[%s:%d]: can't get http_decoder exdata index !!!\n",
+ __FUNCTION__, __LINE__);
+ exit(-1);
+ }
+
+ g_topic_id = session_mq_get_topic_id(st, "HTTP_DECODER_MESSAGE");
+ if (g_topic_id < 0)
+ {
+ printf("[%s:%d]: can't get http_decoder topic id !!!\n",
+ __FUNCTION__, __LINE__);
+ exit(-1);
+ }
+
+ session_mq_subscribe_topic(st, g_topic_id, http_decoder_test_entry, NULL);
+ printf("http_decoder_test_init OK!\n");
+
+ return NULL;
+}
+
+extern "C" void http_decoder_test_exit(void *test_ctx)
+{
+ if (test_ctx != NULL)
+ {
+ FREE(test_ctx);
+ }
+
+ printf("http_decoder_test_exit OK!\n");
+} \ No newline at end of file
diff --git a/test/http_decoder_gtest.h b/test/http_decoder_gtest.h
new file mode 100644
index 0000000..46b0c43
--- /dev/null
+++ b/test/http_decoder_gtest.h
@@ -0,0 +1,114 @@
+#pragma once
+
+#include "http_decoder.h"
+#include "session.h"
+#include "md5.h"
+#include <stdint.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <pcap/pcap.h>
+#include "cJSON.h"
+
+#define TRUE 1
+#define FLASE 0
+
+#define JSON_KEY_VALUE_STRING_MAX_LEN (4096)
+
+#define MAX(a, b) ((a) >= (b) ? (a) : (b))
+#define MIN(a, b) ((a) >= (b) ? (b) : (a))
+
+#define MMALLOC(type, size) ((type *)calloc(1, size))
+#define MFREE(p) \
+ do \
+ { \
+ free(p); \
+ p = NULL; \
+ } while (0)
+
+#if 0
+#define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
+#else
+#define DEBUG_PRINT(fmt, ...)
+#endif
+
+#define EX_DATA_MAX_SIZE 10
+#define PIPELINE_MAX_NUM 8
+
+#define KEY_NAME_REQ_LINE "__REQ_LINE__"
+#define KEY_NAME_RES_LINE "__RES_LINE__"
+#define KEY_NAME_REQ_URL "__REQ_URL__"
+#define KEY_NAME_PAYLOAD_MD5 "__PAYLOAD_MD5__"
+
+#define GTEST_FIX_PAYLOAD_CSTR "<Hello http decoder World!!!>"
+#define GTEST_FIX_PAYLOAD_MD5 "e91e072f772737c7a45013cc3b1a916c"
+
+#define GTEST_HTTP_URL_NAME "__X_HTTP_URL"
+#define GTEST_HTTP_TRANS_NAME "__X_HTTP_TRANSACTION"
+#define GTEST_HTTP_TUPLE4_NAME "__X_HTTP_TUPLE4"
+
+struct fake_exdata_manage
+{
+ char *name;
+ int name_len;
+ void *arg;
+ void (*free_func)(struct session *sess, int idx, void *ex_ptr, void *arg);
+};
+
+struct fake_packet
+{
+ int dir;
+ size_t raw_pkt_data_len;
+ const char *raw_pkt_data; // referred to packet Ethernet MAC header
+ const char *l4_header; // referred to TCP, UDP header
+ size_t payload_data_len;
+ const char *payload_data; // referred to L7 data, such as HTTP, DNS, etc.
+ size_t payload_submit_offset;
+};
+
+enum data_source_type_t
+{
+ __DATA_SOURCE_NULL = 0,
+ DATA_SOURCE_PCAP,
+ DATA_SOURCE_JSON,
+ __DATA_SOURCE_MAX
+};
+
+struct data_src_json_para_t
+{
+ cJSON *json_root;
+ cJSON *current_object;
+ int json_array_size;
+ int current_json_array_idx;
+ char key_value_buf[JSON_KEY_VALUE_STRING_MAX_LEN];
+};
+
+struct fake_stellar
+{
+ struct fake_exdata_manage fake_exdata_mgr[EX_DATA_MAX_SIZE];
+ int tcp_mss;
+ void *http_decoder_ctx;
+ void *http_http_plug_ctx;
+ int (*http_decoder_entry)(struct session *sess, int events,
+ const struct packet *pkt, void *cb_arg);
+ cJSON *http_plug_test_result_root;
+ enum data_source_type_t data_source_type;
+ const char *benchmark_json_file_name;
+ const char *data_source_file_name;
+ cJSON *load_benchmark_json_root;
+ pcap_t *pcap_ins;
+
+ struct data_src_json_para_t data_src_json_para;
+};
+
+struct fake_session
+{
+ enum session_type type;
+ struct fake_stellar *fst;
+ struct fake_packet *fpkt;
+ int events;
+ int tcp_mss;
+ enum session_addr_type addr_type;
+ struct session_addr *addr;
+ char *readable_addr_cstr;
+ void *plug_exdata_array[EX_DATA_MAX_SIZE];
+};
diff --git a/test/http_decoder_stub.cpp b/test/http_decoder_stub.cpp
new file mode 100644
index 0000000..c879630
--- /dev/null
+++ b/test/http_decoder_stub.cpp
@@ -0,0 +1,250 @@
+/*
+ Http Decoder Google Test stub module
+*/
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include "http_decoder.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+#include "session.h"
+#include "session_exdata.h"
+#include "session_mq.h"
+#include "stellar.h"
+#include "http_decoder_gtest.h"
+#include "MESA_jump_layer.h"
+
+ static int g_topic_id = -1;
+ static msg_free_cb_func *g_msg_free_cb = NULL;
+ static void *g_msg_free_cb_arg = NULL;
+
+ extern struct fake_stellar *g_fake_stellar;
+
+ int commit_test_result_json(cJSON *node, const char *name)
+ {
+ assert(node != NULL || name != NULL);
+ if (g_fake_stellar->http_plug_test_result_root)
+ {
+ cJSON_AddStringToObject(node, "__X_HTTP_RESULT_INDEX", name);
+ cJSON_AddItemToArray(g_fake_stellar->http_plug_test_result_root, node);
+ return 0;
+ }
+ return -1;
+ }
+
+ int packet_get_direction(const struct packet *pkt)
+ {
+ struct fake_packet *fpkt = (struct fake_packet *)pkt;
+ return fpkt->dir;
+ }
+
+ int session_event_assign(struct session_event *ev, struct stellar *st, struct session *sess, int events, session_event_cb_func *cb, void *cb_arg)
+ {
+ DEBUG_PRINT("todo: fake session_event_assign()\n");
+ return 0;
+ }
+
+ const char *session_get0_readable_addr(struct session *sess)
+ {
+ struct fake_session *fses = (struct fake_session *)sess;
+
+ if (fses->readable_addr_cstr)
+ {
+ return fses->readable_addr_cstr;
+ }
+
+ char ip_src_buf[INET6_ADDRSTRLEN] = {};
+ char ip_dst_buf[INET6_ADDRSTRLEN] = {};
+ char port_src_buf[16] = {};
+ char port_dst_buf[16] = {};
+ char tuple4_buf[256] = {};
+
+ if (SESSION_ADDR_TYPE_IPV4_TCP == fses->addr_type)
+ {
+ inet_ntop(AF_INET, &fses->addr->ipv4.saddr, ip_src_buf, INET_ADDRSTRLEN);
+ inet_ntop(AF_INET, &fses->addr->ipv4.daddr, ip_dst_buf, INET_ADDRSTRLEN);
+ sprintf(port_src_buf, "%u", ntohs(fses->addr->ipv4.sport));
+ sprintf(port_dst_buf, "%u", ntohs(fses->addr->ipv4.dport));
+ }
+ else
+ {
+ inet_ntop(AF_INET6, fses->addr->ipv6.saddr, ip_src_buf, INET6_ADDRSTRLEN);
+ inet_ntop(AF_INET6, fses->addr->ipv6.daddr, ip_dst_buf, INET6_ADDRSTRLEN);
+ sprintf(port_src_buf, "%u", ntohs(fses->addr->ipv6.sport));
+ sprintf(port_dst_buf, "%u", ntohs(fses->addr->ipv6.dport));
+ }
+
+ snprintf(tuple4_buf, sizeof(tuple4_buf), "%s.%s>%s.%s", ip_src_buf, port_src_buf, ip_dst_buf, port_dst_buf);
+ fses->readable_addr_cstr = MMALLOC(char, strlen(tuple4_buf) + 1);
+ memcpy(fses->readable_addr_cstr, tuple4_buf, strlen(tuple4_buf));
+
+ return fses->readable_addr_cstr;
+ }
+
+ struct session_addr *session_get0_addr(struct session *sess, enum session_addr_type *addr_type)
+ {
+ struct fake_session *fses = (struct fake_session *)sess;
+ *addr_type = fses->addr_type;
+ return fses->addr;
+ }
+
+ static int __find_ex_data(struct fake_stellar *fst, const char *name)
+ {
+ int find_name_len = strlen(name);
+
+ for (int i = 0; i < EX_DATA_MAX_SIZE; i++)
+ {
+ if ((fst->fake_exdata_mgr[i].name != NULL) && (strncasecmp(name, fst->fake_exdata_mgr[i].name, find_name_len) == 0) && (find_name_len == fst->fake_exdata_mgr[i].name_len))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ static int __save_ex_data(struct fake_stellar *fst, const char *name, session_ex_free *free_func, void *arg)
+ {
+ for (int i = 0; i < EX_DATA_MAX_SIZE; i++)
+ {
+ if (fst->fake_exdata_mgr[i].name == NULL && fst->fake_exdata_mgr[i].name_len == 0)
+ {
+ fst->fake_exdata_mgr[i].name = MMALLOC(char, strlen(name) + 1);
+ fst->fake_exdata_mgr[i].name_len = strlen(name) + 1;
+ memcpy(fst->fake_exdata_mgr[i].name, name, strlen(name));
+ fst->fake_exdata_mgr[i].free_func = free_func;
+ fst->fake_exdata_mgr[i].arg = arg;
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ int stellar_session_get_ex_new_index(struct stellar *st, const char *name, session_ex_free *free_func, void *arg)
+ {
+ int ex_id = __find_ex_data((struct fake_stellar *)st, name);
+ if (-1 == ex_id)
+ {
+ ex_id = __save_ex_data((struct fake_stellar *)st, name, free_func, arg);
+ }
+
+ return ex_id;
+ }
+
+ int session_mq_get_topic_id(struct stellar *st, const char *topic_name)
+ {
+ return g_topic_id;
+ }
+
+ int session_get_current_thread_id(struct session *sess)
+ {
+ return 0;
+ }
+
+ int session_mq_destroy_topic(struct stellar *st, int topic_id)
+ {
+ return 0;
+ }
+
+ int session_set_ex_data(struct session *sess, int idx, void *ex_ptr)
+ {
+ struct fake_session *fses = (struct fake_session *)sess;
+ fses->plug_exdata_array[idx] = ex_ptr;
+
+ return 0;
+ }
+
+ void *session_get_ex_data(struct session *sess, int idx)
+ {
+ struct fake_session *fses = (struct fake_session *)sess;
+ return fses->plug_exdata_array[idx];
+ }
+
+ void hdd_session_free_exdata(struct fake_session *fake_ses)
+ {
+ for (int i = 0; i < EX_DATA_MAX_SIZE; i++)
+ {
+ if (fake_ses->plug_exdata_array[i] != NULL)
+ {
+ fake_ses->fst->fake_exdata_mgr[i].free_func((struct session *)fake_ses, i, fake_ses->plug_exdata_array[i], NULL);
+ }
+ }
+ }
+
+ int stellar_plugin_register(struct stellar *st, int events, session_event_cb_func *cb, void *cb_arg)
+ {
+ return 0; // fix plugin id
+ }
+
+ extern int http_decoder_test_entry(struct session *sess, int topic_id, const void *data, void *cb_arg);
+ int session_mq_publish_message(struct session *sess, int topic_id, void *data)
+ {
+ http_decoder_test_entry(sess, topic_id, data, NULL);
+
+ g_msg_free_cb(data, g_msg_free_cb_arg);
+ return 0;
+ }
+
+ int stellar_get_worker_thread_num(struct stellar *st)
+ {
+ return 1;
+ }
+
+ int session_mq_create_topic(struct stellar *st, const char *topic_name, msg_free_cb_func *free_cb, void *cb_arg)
+ {
+ g_msg_free_cb = free_cb;
+ g_msg_free_cb_arg = cb_arg;
+
+ g_topic_id = 0; // KISS, use fix value
+ return g_topic_id;
+ }
+
+ const char *session_get0_current_payload(struct session *sess, size_t *payload_len)
+ {
+ struct fake_session *fses = (struct fake_session *)sess;
+ struct fake_packet *fpkt = fses->fpkt;
+ const char *payload_ptr = NULL;
+
+ if (!fpkt || !fpkt->payload_data || fpkt->payload_data_len == 0)
+ {
+ return NULL;
+ }
+
+ int submit_len = MIN(fses->tcp_mss, fpkt->payload_data_len - fpkt->payload_submit_offset);
+ if(submit_len <= 0)
+ {
+ *payload_len = 0;
+ return NULL;
+ }
+ payload_ptr = fpkt->payload_data + fpkt->payload_submit_offset;
+ *payload_len = submit_len;
+ fpkt->payload_submit_offset += submit_len;
+
+ return payload_ptr;
+ }
+
+ struct session_event *session_get_intrinsic_event(struct session *sess, int plugin_id)
+ {
+ return NULL;
+ }
+
+ int session_is_inner_most(struct session *sess, uint64_t *flag)
+ {
+ return 1; // no tunnel
+ }
+
+ int session_mq_subscribe_topic(struct stellar *st, int topic_id, on_msg_cb_func *sub_cb, void *cb_arg)
+ {
+ // to do
+ return 0;
+ }
+
+#ifdef __cplusplus
+}
+#endif \ No newline at end of file
diff --git a/test/http_pcap/http_6over4_single_trans.pcap b/test/http_pcap/http_6over4_single_trans.pcap
new file mode 100644
index 0000000..6b074e5
--- /dev/null
+++ b/test/http_pcap/http_6over4_single_trans.pcap
Binary files differ
diff --git a/test/http_pcap/http_chunked_res_gzip.pcap b/test/http_pcap/http_chunked_res_gzip.pcap
new file mode 100644
index 0000000..fa237fb
--- /dev/null
+++ b/test/http_pcap/http_chunked_res_gzip.pcap
Binary files differ
diff --git a/test/http_pcap/http_connect_flood.pcap b/test/http_pcap/http_connect_flood.pcap
new file mode 100644
index 0000000..11b7e14
--- /dev/null
+++ b/test/http_pcap/http_connect_flood.pcap
Binary files differ
diff --git a/test/http_pcap/http_get_encoded_uri.pcap b/test/http_pcap/http_get_encoded_uri.pcap
new file mode 100644
index 0000000..633b50b
--- /dev/null
+++ b/test/http_pcap/http_get_encoded_uri.pcap
Binary files differ
diff --git a/test/http_pcap/http_get_long_cookie.pcap b/test/http_pcap/http_get_long_cookie.pcap
new file mode 100644
index 0000000..8857615
--- /dev/null
+++ b/test/http_pcap/http_get_long_cookie.pcap
Binary files differ
diff --git a/test/http_pcap/http_get_malformed.pcap b/test/http_pcap/http_get_malformed.pcap
new file mode 100644
index 0000000..08deaeb
--- /dev/null
+++ b/test/http_pcap/http_get_malformed.pcap
Binary files differ
diff --git a/test/http_pcap/http_get_multi_trans.pcap b/test/http_pcap/http_get_multi_trans.pcap
new file mode 100644
index 0000000..9c0d2e7
--- /dev/null
+++ b/test/http_pcap/http_get_multi_trans.pcap
Binary files differ
diff --git a/test/http_pcap/http_get_req_pipeline.pcap b/test/http_pcap/http_get_req_pipeline.pcap
new file mode 100644
index 0000000..f805493
--- /dev/null
+++ b/test/http_pcap/http_get_req_pipeline.pcap
Binary files differ
diff --git a/test/http_pcap/http_get_single_trans.pcap b/test/http_pcap/http_get_single_trans.pcap
new file mode 100644
index 0000000..a4b6bea
--- /dev/null
+++ b/test/http_pcap/http_get_single_trans.pcap
Binary files differ
diff --git a/test/http_pcap/http_hdr_truncated_after_kv.pcap b/test/http_pcap/http_hdr_truncated_after_kv.pcap
new file mode 100644
index 0000000..de9f018
--- /dev/null
+++ b/test/http_pcap/http_hdr_truncated_after_kv.pcap
Binary files differ
diff --git a/test/http_pcap/http_hdr_truncated_in_kv.pcap b/test/http_pcap/http_hdr_truncated_in_kv.pcap
new file mode 100644
index 0000000..b4015c9
--- /dev/null
+++ b/test/http_pcap/http_hdr_truncated_in_kv.pcap
Binary files differ
diff --git a/test/http_pcap/http_hdr_value_empty.pcap b/test/http_pcap/http_hdr_value_empty.pcap
new file mode 100644
index 0000000..bb22243
--- /dev/null
+++ b/test/http_pcap/http_hdr_value_empty.pcap
Binary files differ
diff --git a/test/http_pcap/http_hdrs_exceed_maximum.pcap b/test/http_pcap/http_hdrs_exceed_maximum.pcap
new file mode 100644
index 0000000..537bffa
--- /dev/null
+++ b/test/http_pcap/http_hdrs_exceed_maximum.pcap
Binary files differ
diff --git a/test/http_pcap/http_multi_parse_error.pcap b/test/http_pcap/http_multi_parse_error.pcap
new file mode 100644
index 0000000..b57e275
--- /dev/null
+++ b/test/http_pcap/http_multi_parse_error.pcap
Binary files differ
diff --git a/test/http_pcap/http_no_content_length.pcap b/test/http_pcap/http_no_content_length.pcap
new file mode 100644
index 0000000..28e6881
--- /dev/null
+++ b/test/http_pcap/http_no_content_length.pcap
Binary files differ
diff --git a/test/http_pcap/http_over_pppoe.pcap b/test/http_pcap/http_over_pppoe.pcap
new file mode 100644
index 0000000..a6587cb
--- /dev/null
+++ b/test/http_pcap/http_over_pppoe.pcap
Binary files differ
diff --git a/test/http_pcap/http_over_tcp_keepalive.pcap b/test/http_pcap/http_over_tcp_keepalive.pcap
new file mode 100644
index 0000000..5b2db18
--- /dev/null
+++ b/test/http_pcap/http_over_tcp_keepalive.pcap
Binary files differ
diff --git a/test/http_pcap/http_over_tls.pcap b/test/http_pcap/http_over_tls.pcap
new file mode 100644
index 0000000..71c557e
--- /dev/null
+++ b/test/http_pcap/http_over_tls.pcap
Binary files differ
diff --git a/test/http_pcap/http_post_multipart_form_data.pcap b/test/http_pcap/http_post_multipart_form_data.pcap
new file mode 100644
index 0000000..052d326
--- /dev/null
+++ b/test/http_pcap/http_post_multipart_form_data.pcap
Binary files differ
diff --git a/test/http_pcap/http_req_1byte_sliding_window.pcap b/test/http_pcap/http_req_1byte_sliding_window.pcap
new file mode 100644
index 0000000..632c676
--- /dev/null
+++ b/test/http_pcap/http_req_1byte_sliding_window.pcap
Binary files differ
diff --git a/test/http_pcap/http_res_1byte_sliding_window.pcap b/test/http_pcap/http_res_1byte_sliding_window.pcap
new file mode 100644
index 0000000..6d1d6a4
--- /dev/null
+++ b/test/http_pcap/http_res_1byte_sliding_window.pcap
Binary files differ
diff --git a/test/http_pcap/http_res_gzip.pcap b/test/http_pcap/http_res_gzip.pcap
new file mode 100644
index 0000000..04b9998
--- /dev/null
+++ b/test/http_pcap/http_res_gzip.pcap
Binary files differ
diff --git a/test/http_pcap/http_trans_pipeline.pcap b/test/http_pcap/http_trans_pipeline.pcap
new file mode 100644
index 0000000..43438e1
--- /dev/null
+++ b/test/http_pcap/http_trans_pipeline.pcap
Binary files differ
diff --git a/test/http_pcap/http_tunnel_for_pop3.pcap b/test/http_pcap/http_tunnel_for_pop3.pcap
new file mode 100644
index 0000000..6df9669
--- /dev/null
+++ b/test/http_pcap/http_tunnel_for_pop3.pcap
Binary files differ
diff --git a/test/http_pcap/http_upgrade_http2.pcap b/test/http_pcap/http_upgrade_http2.pcap
new file mode 100644
index 0000000..a77847a
--- /dev/null
+++ b/test/http_pcap/http_upgrade_http2.pcap
Binary files differ
diff --git a/test/http_pcap/http_upgrade_websocket.pcap b/test/http_pcap/http_upgrade_websocket.pcap
new file mode 100644
index 0000000..b823d7e
--- /dev/null
+++ b/test/http_pcap/http_upgrade_websocket.pcap
Binary files differ
diff --git a/test/http_pcap/http_url_test_with_host.pcap b/test/http_pcap/http_url_test_with_host.pcap
new file mode 100644
index 0000000..8a57c4a
--- /dev/null
+++ b/test/http_pcap/http_url_test_with_host.pcap
Binary files differ
diff --git a/test/http_pcap/http_url_test_without_host.pcap b/test/http_pcap/http_url_test_without_host.pcap
new file mode 100644
index 0000000..0036379
--- /dev/null
+++ b/test/http_pcap/http_url_test_without_host.pcap
Binary files differ
diff --git a/test/http_pcap/non_http.pcap b/test/http_pcap/non_http.pcap
new file mode 100644
index 0000000..931b43b
--- /dev/null
+++ b/test/http_pcap/non_http.pcap
Binary files differ
diff --git a/test/md5.c b/test/md5.c
new file mode 100644
index 0000000..13a5d16
--- /dev/null
+++ b/test/md5.c
@@ -0,0 +1,356 @@
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software. */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stdio.h>
+#include "md5.h"
+
+ /* Constants for MD5Transform routine. */
+
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+ static void MD5Transform PROTO_LIST((UINT4[4], unsigned char[64]));
+ static void Encode PROTO_LIST((unsigned char *, UINT4 *, unsigned int));
+ static void Decode PROTO_LIST((UINT4 *, unsigned char *, unsigned int));
+ static void MD5_memcpy PROTO_LIST((POINTER, POINTER, unsigned int));
+ static void MD5_memset PROTO_LIST((POINTER, int, unsigned int));
+
+ static unsigned char PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+/* F, G, H and I are basic MD5 functions. */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits. */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+Rotation is separate from addition to prevent recomputation. */
+#define FF(a, b, c, d, x, s, ac) \
+ { \
+ (a) += F((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) \
+ { \
+ (a) += G((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) \
+ { \
+ (a) += H((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) \
+ { \
+ (a) += I((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT((a), (s)); \
+ (a) += (b); \
+ }
+
+ /* MD5 initialization. Begins an MD5 operation, writing a new context. */
+ void MD5Init(MD5_CTX *context)
+ {
+ context->count[0] = context->count[1] = 0;
+ /* Load magic initialization constants.*/
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+ }
+
+ /* MD5 block update operation. Continues an MD5 message-digest
+ operation, processing another message block, and updating the
+ context. */
+ void MD5Update(
+ MD5_CTX *context, /* context */
+ unsigned char *input, /* input block */
+ unsigned int inputLen) /* length of input block */
+ {
+ unsigned int i, index, partLen;
+
+ /* Compute number of bytes mod 64 */
+ index = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((UINT4)inputLen << 3))
+
+ < ((UINT4)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((UINT4)inputLen >> 29);
+
+ partLen = 64 - index;
+
+ /* Transform as many times as possible.*/
+ if (inputLen >= partLen)
+ {
+ MD5_memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen);
+ MD5Transform(context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform(context->state, &input[i]);
+
+ index = 0;
+ }
+ else
+ i = 0;
+
+ /* Buffer remaining input */
+ MD5_memcpy((POINTER)&context->buffer[index], (POINTER)&input[i],
+ inputLen - i);
+ }
+
+ /* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ the message digest and zeroizing the context. */
+ void MD5Final(
+ unsigned char digest[16], /* message digest */
+ MD5_CTX *context) /* context */
+ {
+ unsigned char bits[8];
+ unsigned int index, padLen;
+
+ /* Save number of bits */
+ Encode(bits, context->count, 8);
+
+ /* Pad out to 56 mod 64.*/
+ index = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (index < 56) ? (56 - index) : (120 - index);
+ MD5Update(context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ MD5Update(context, bits, 8);
+
+ /* Store state in digest */
+ Encode(digest, context->state, 16);
+
+ /* Zeroize sensitive information.*/
+ MD5_memset((POINTER)context, 0, sizeof(*context));
+ }
+
+ /* MD5 basic transformation. Transforms state based on block. */
+ static void MD5Transform(
+ UINT4 state[4],
+ unsigned char block[64])
+ {
+ UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+ Decode(x, block, 64);
+
+ /* Round 1 */
+ FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */
+
+ GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information. */
+ MD5_memset((POINTER)x, 0, sizeof(x));
+ }
+
+ /* Encodes input (UINT4) into output (unsigned char). Assumes len is
+ a multiple of 4. */
+ static void Encode(
+ unsigned char *output,
+ const UINT4 *input,
+ unsigned int len)
+ {
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ {
+ output[j] = (unsigned char)(input[i] & 0xff);
+ output[j + 1] = (unsigned char)((input[i] >> 8) & 0xff);
+ output[j + 2] = (unsigned char)((input[i] >> 16) & 0xff);
+ output[j + 3] = (unsigned char)((input[i] >> 24) & 0xff);
+ }
+ }
+
+ /* Decodes input (unsigned char) into output (UINT4). Assumes len is
+ a multiple of 4. */
+ static void Decode(
+ UINT4 *output,
+ unsigned char *input,
+ unsigned int len)
+ {
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ output[i] = ((UINT4)input[j]) | (((UINT4)input[j + 1]) << 8) |
+ (((UINT4)input[j + 2]) << 16) | (((UINT4)input[j + 3]) << 24);
+ }
+
+ /* Note: Replace "for loop" with standard memcpy if possible. */
+
+ static void MD5_memcpy(
+ POINTER output,
+ const POINTER input,
+ unsigned int len)
+ {
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+
+ output[i] = input[i];
+ }
+
+ /* Note: Replace "for loop" with standard memset if possible. */
+ static void MD5_memset(
+ POINTER output,
+ int value,
+ unsigned int len)
+ {
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ ((char *)output)[i] = (char)value;
+ }
+
+ char *MESA_MD5_sum_str(unsigned char *raw_data, unsigned int raw_data_len, char result[33])
+ {
+ int i;
+ MD5_CTX context;
+ unsigned char digest[16];
+
+ MD5Init(&context);
+ MD5Update(&context, raw_data, raw_data_len);
+ MD5Final(digest, &context);
+
+ for (i = 0; i < 16; i++)
+ sprintf(result + 2 * i, "%02x", digest[i]);
+ result[32] = 0;
+
+ return result;
+ }
+
+ char *MESA_MD5_sum_bin(unsigned char *raw_data, unsigned int raw_data_len, char result[16])
+ {
+ MD5_CTX context;
+
+ MD5Init(&context);
+ MD5Update(&context, raw_data, raw_data_len);
+ MD5Final(result, &context);
+
+ return result;
+ }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/test/md5.h b/test/md5.h
new file mode 100644
index 0000000..9f5b5d2
--- /dev/null
+++ b/test/md5.h
@@ -0,0 +1,70 @@
+/* MD5.H - header file for MD5C.C */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software. */
+#ifndef __MD5_H
+#define __MD5_H
+
+#ifndef PROTOTYPES
+#define PROTOTYPES 0
+#endif
+
+/* POINTER defines a generic pointer type */
+typedef unsigned char *POINTER;
+
+/* UINT2 defines a two byte word */
+typedef unsigned short int UINT2;
+
+/* UINT4 defines a four byte word */
+//typedef unsigned long int UINT4;
+typedef unsigned int UINT4;
+
+/* PROTO_LIST is defined depending on how PROTOTYPES is defined above.
+ If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it
+ returns an empty list. */
+
+#if PROTOTYPES
+#define PROTO_LIST(list) list
+#else
+#define PROTO_LIST(list) ()
+#endif
+
+/* MD5 context. */
+typedef struct {
+ UINT4 state[4]; /* state (ABCD) */
+ UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ unsigned char buffer[64]; /* input buffer */
+} MD5_CTX;
+
+void MD5Init PROTO_LIST ((MD5_CTX *));
+void MD5Update PROTO_LIST ((MD5_CTX *, unsigned char *, unsigned int));
+void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *));
+
+#ifdef __cplusplus
+extern "C"
+{
+char *MESA_MD5_sum_str(unsigned char *raw_data, unsigned int raw_data_len, char result[33]);
+char *MESA_MD5_sum_bin(unsigned char *raw_data, unsigned int raw_data_len, char result[16]);
+}
+#endif
+
+
+#endif
diff --git a/test/test_env/conflist.inf b/test/test_env/conflist.inf
new file mode 100644
index 0000000..2e8144d
--- /dev/null
+++ b/test/test_env/conflist.inf
@@ -0,0 +1,9 @@
+[platform]
+./plug/stellar_on_sapp/start_loader.inf
+
+
+[protocol]
+
+
+[business]
+#./plug/stellar_on_sapp/defer_loader.inf
diff --git a/test/test_env/sapp4.el8.x86_64.rpm b/test/test_env/sapp4.el8.x86_64.rpm
new file mode 100644
index 0000000..e43fe2f
--- /dev/null
+++ b/test/test_env/sapp4.el8.x86_64.rpm
Binary files differ
diff --git a/test/test_env/spec.toml b/test/test_env/spec.toml
new file mode 100644
index 0000000..b8c7eb9
--- /dev/null
+++ b/test/test_env/spec.toml
@@ -0,0 +1,11 @@
+# stellar_plugin.toml
+#
+[[plugin]]
+path = "./stellar_plugin/http_decoder.so"
+init = "http_decoder_init"
+exit = "http_decoder_exit"
+
+[[plugin]]
+path = "./stellar_plugin/http_decoder_test.so"
+init = "http_decoder_test_init"
+exit = "http_decoder_test_exit" \ No newline at end of file
diff --git a/test/test_env/start_loader.inf b/test/test_env/start_loader.inf
new file mode 100644
index 0000000..89b2f94
--- /dev/null
+++ b/test/test_env/start_loader.inf
@@ -0,0 +1,17 @@
+[PLUGINFO]
+PLUGNAME=stellar_start_loader
+SO_PATH=./plug/stellar_on_sapp/stellar_on_sapp.so
+INIT_FUNC=STELLAR_START_LOADER_INIT
+DESTROY_FUNC=STELLAR_START_LOADER_EXIT
+
+#[TCP_ALL]
+#FUNC_FLAG=ALL
+#FUNC_NAME=stellar_on_sapp_tcpall_entry
+
+[TCP]
+FUNC_FLAG=ALL
+FUNC_NAME=stellar_on_sapp_tcp_entry
+
+[UDP]
+FUNC_FLAG=ALL
+FUNC_NAME=stellar_on_sapp_udp_entry \ No newline at end of file
diff --git a/test/test_env/tsg_l7_protocol.conf b/test/test_env/tsg_l7_protocol.conf
new file mode 100644
index 0000000..1075a8f
--- /dev/null
+++ b/test/test_env/tsg_l7_protocol.conf
@@ -0,0 +1,57 @@
+#TYPE:1:UCHAR,2:USHORT,3:USTRING,4:ULOG,5:USTRING,6:FILE,7:UBASE64,8:PACKET
+#TYPE FIELD VALUE
+STRING UNCATEGORIZED 8000
+#STRING UNCATEGORIZED 8001
+#STRING UNKNOWN_OTHER 8002
+STRING DNS 32
+STRING FTP 45
+STRING FTPS 751
+STRING HTTP 67
+STRING HTTPS 68
+STRING ICMP 70
+STRING IKE 8003
+STRING MAIL 8004
+STRING IMAP 75
+STRING IMAPS 76
+STRING IPSEC 85
+STRING XMPP 94
+STRING L2TP 98
+STRING NTP 137
+STRING POP3 147
+STRING POP3S 148
+STRING PPTP 153
+STRING QUIC 2521
+STRING SIP 182
+STRING SMB 185
+STRING SMTP 186
+STRING SMTPS 187
+STRING SPDY 1469
+STRING SSH 198
+STRING SSL 199
+STRING SOCKS 8005
+STRING TELNET 209
+STRING DHCP 29
+STRING RADIUS 158
+STRING OPENVPN 336
+STRING STUN 201
+STRING TEREDO 555
+STRING DTLS 1291
+STRING DoH 8006
+STRING ISAKMP 92
+STRING MDNS 3835
+STRING NETBIOS 129
+STRING NETFLOW 130
+STRING RDP 150
+STRING RTCP 174
+STRING RTP 175
+STRING SLP 8007
+STRING SNMP 190
+STRING SSDP 197
+STRING TFTP 211
+STRING BJNP 2481
+STRING LDAP 100
+STRING RTMP 337
+STRING RTSP 176
+STRING ESNI 8008
+STRING QQ 156
+STRING WeChat 1296
diff --git a/test/test_result_json/http_6over4_single_trans.json b/test/test_result_json/http_6over4_single_trans.json
new file mode 100644
index 0000000..4f6244a
--- /dev/null
+++ b/test/test_result_json/http_6over4_single_trans.json
@@ -0,0 +1,33 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "2001:da8:200:900e:200:5efe:d24d:58a3.52556>2600:140e:6::1702:1058.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/ncsi.txt",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Connection": "Close",
+ "User-Agent": "Microsoft NCSI",
+ "Host": "www.msftncsi.com",
+ "__X_HTTP_URL": "www.msftncsi.com/ncsi.txt",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Content-Length": "14",
+ "Date": "Tue, 01 Dec 2015 03:41:27 GMT",
+ "Connection": "close",
+ "Content-Type": "text/plain",
+ "Cache-Control": "max-age=30, must-revalidate",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_chunked_res_gzip.json b/test/test_result_json/http_chunked_res_gzip.json
new file mode 100644
index 0000000..4da91fd
--- /dev/null
+++ b/test/test_result_json/http_chunked_res_gzip.json
@@ -0,0 +1,41 @@
+[{
+ "__X_HTTP_TUPLE4": "127.0.0.1.33412>127.0.0.1.8080",
+ "__X_HTTP_RESULT_INDEX": "0"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "www.wireshark.org:8080",
+ "User-Agent": "curl/7.46.0",
+ "Accept": "*/*",
+ "Connection": "close",
+ "Accept-Encoding": "chunked, gzip",
+ "__X_HTTP_URL": "www.wireshark.org:8080/",
+ "__X_HTTP_RESULT_INDEX": "1"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "cloudflare-nginx",
+ "Date": "Wed, 06 Jan 2016 20:42:10 GMT",
+ "Content-Type": "text/html",
+ "Transfer-Encoding": "chunked",
+ "Connection": "close",
+ "Set-Cookie": "__cfduid=d8d37b52eaa3137bdfd7fd67a4ffc8a7a1452112929; expires=Thu, 05-Jan-17 20:42:09 GMT; path=/; domain=.wireshark.org; HttpOnly",
+ "X-Frame-Options": "SAMEORIGIN",
+ "Strict-Transport-Security": "max-age=31536000;",
+ "X-Slogan": "It's a great product with a great story to tell. I'm pumped!",
+ "X-Mod-Pagespeed": "1.9.32.11-7550",
+ "Vary": "Accept-Encoding",
+ "Cache-control": "max-age=0, no-cache, no-store",
+ "X-Slogan1": "Go deep.",
+ "CF-RAY": "260a3f709d7b0761-AMS",
+ "Content-Encoding": "gzip",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }]
diff --git a/test/test_result_json/http_connect_flood.json b/test/test_result_json/http_connect_flood.json
new file mode 100644
index 0000000..199c038
--- /dev/null
+++ b/test/test_result_json/http_connect_flood.json
@@ -0,0 +1,686 @@
+[
+ {
+ "Tuple4": "10.128.0.2.18762>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.16) Gecko/20110929 Iceweasel/3.5.16",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_1"
+ },
+ {
+ "Tuple4": "10.128.0.2.18746>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_2"
+ },
+ {
+ "Tuple4": "10.128.0.2.18744>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_3"
+ },
+ {
+ "Tuple4": "10.128.0.2.18748>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_4"
+ },
+ {
+ "Tuple4": "10.128.0.2.18750>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_5"
+ },
+ {
+ "Tuple4": "10.128.0.2.18752>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_6"
+ },
+ {
+ "Tuple4": "10.128.0.2.18754>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_7"
+ },
+ {
+ "Tuple4": "10.128.0.2.18756>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_8"
+ },
+ {
+ "Tuple4": "10.128.0.2.18758>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_9"
+ },
+ {
+ "Tuple4": "10.128.0.2.18760>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_10"
+ },
+ {
+ "Tuple4": "10.128.0.2.18762>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:38 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_11"
+ },
+ {
+ "Tuple4": "10.128.0.2.18768>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Safari/5.00 (Macintosh; U; en)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_12"
+ },
+ {
+ "Tuple4": "10.128.0.2.18766>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_13"
+ },
+ {
+ "Tuple4": "10.128.0.2.18770>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Opera/9.00 (Windows NT 5.1; U; en)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_14"
+ },
+ {
+ "Tuple4": "10.128.0.2.18772>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Opera/9.00 (Windows NT 5.1; U; en)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_15"
+ },
+ {
+ "Tuple4": "10.128.0.2.18776>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "GooglePocket/2.1 ( http://www.googlePocket.com/Pocket.html)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_16"
+ },
+ {
+ "Tuple4": "10.128.0.2.18774>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:31.0) Gecko/20100101 Firefox/31.0 Iceweasel/31.4.0",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_17"
+ },
+ {
+ "Tuple4": "10.128.0.2.18780>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.4b) Gecko/20030505 Mozilla Firebird/0.6",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_18"
+ },
+ {
+ "Tuple4": "10.128.0.2.18778>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "DoCoMo/2.0 SH902i (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_19"
+ },
+ {
+ "Tuple4": "10.128.0.2.18782>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "IE/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 1.1.4322;)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_20"
+ },
+ {
+ "Tuple4": "10.128.0.2.18784>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.16) Gecko/20110929 Iceweasel/3.5.16",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_21"
+ },
+ {
+ "Tuple4": "10.128.0.2.18768>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_22"
+ },
+ {
+ "Tuple4": "10.128.0.2.18766>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_23"
+ },
+ {
+ "Tuple4": "10.128.0.2.18770>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_24"
+ },
+ {
+ "Tuple4": "10.128.0.2.18772>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_25"
+ },
+ {
+ "Tuple4": "10.128.0.2.18776>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_26"
+ },
+ {
+ "Tuple4": "10.128.0.2.18780>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_27"
+ },
+ {
+ "Tuple4": "10.128.0.2.18774>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_28"
+ },
+ {
+ "Tuple4": "10.128.0.2.18778>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_29"
+ },
+ {
+ "Tuple4": "10.128.0.2.18782>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_30"
+ },
+ {
+ "Tuple4": "10.128.0.2.18784>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:39 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_31"
+ },
+ {
+ "Tuple4": "10.128.0.2.18790>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Safari/5.00 (Macintosh; U; en)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_32"
+ },
+ {
+ "Tuple4": "10.128.0.2.18792>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_33"
+ },
+ {
+ "Tuple4": "10.128.0.2.18796>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Opera/9.00 (Windows NT 5.1; U; en)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_34"
+ },
+ {
+ "Tuple4": "10.128.0.2.18794>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Opera/9.00 (Windows NT 5.1; U; en)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_35"
+ },
+ {
+ "Tuple4": "10.128.0.2.18798>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "GooglePocket/2.1 ( http://www.googlePocket.com/Pocket.html)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_36"
+ },
+ {
+ "Tuple4": "10.128.0.2.18800>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.4b) Gecko/20030505 Mozilla Firebird/0.6",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_37"
+ },
+ {
+ "Tuple4": "10.128.0.2.18804>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "DoCoMo/2.0 SH902i (compatible; Y!J-SRD/1.0; http://help.yahoo.co.jp/help/jp/search/indexing/indexing-27.html)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_38"
+ },
+ {
+ "Tuple4": "10.128.0.2.18802>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:31.0) Gecko/20100101 Firefox/31.0 Iceweasel/31.4.0",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_39"
+ },
+ {
+ "Tuple4": "10.128.0.2.18806>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "IE/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 1.1.4322;)",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_40"
+ },
+ {
+ "Tuple4": "10.128.0.2.18808>10.0.0.2.80",
+ "method": "CONNECT",
+ "uri": "test.mazebolt.com:80",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.mazebolt.com",
+ "User-Agent": "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.16) Gecko/20110929 Iceweasel/3.5.16",
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate",
+ "Content-Length": "0",
+ "name": "HTTP_DECODER_RESULT_41"
+ },
+ {
+ "Tuple4": "10.128.0.2.18790>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_42"
+ },
+ {
+ "Tuple4": "10.128.0.2.18792>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_43"
+ },
+ {
+ "Tuple4": "10.128.0.2.18796>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_44"
+ },
+ {
+ "Tuple4": "10.128.0.2.18798>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_45"
+ },
+ {
+ "Tuple4": "10.128.0.2.18794>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_46"
+ },
+ {
+ "Tuple4": "10.128.0.2.18800>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_47"
+ },
+ {
+ "Tuple4": "10.128.0.2.18804>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_48"
+ },
+ {
+ "Tuple4": "10.128.0.2.18802>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_49"
+ },
+ {
+ "Tuple4": "10.128.0.2.18806>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_50"
+ },
+ {
+ "Tuple4": "10.128.0.2.18808>10.0.0.2.80",
+ "res_version": "1.1",
+ "res_status": "Forbidden",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 403,
+ "Date": "Thu, 03 Sep 2020 10:26:41 GMT",
+ "Server": "Apache",
+ "Content-Length": "199",
+ "Content-Type": "text/html; charset=iso-8859-1",
+ "name": "HTTP_DECODER_RESULT_51"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_get_encoded_uri.json b/test/test_result_json/http_get_encoded_uri.json
new file mode 100644
index 0000000..189bf88
--- /dev/null
+++ b/test/test_result_json/http_get_encoded_uri.json
@@ -0,0 +1,71 @@
+[{
+ "__X_HTTP_TUPLE4": "192.168.117.60.39655>58.16.70.122.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "POST",
+ "uri": "/disAll/tcCertType.html",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
+ "Accept": "*/*",
+ "Accept-Language": "en-US,en;q=0.8,en-us,en;q=0.5",
+ "Origin": "http://58.16.70.122",
+ "X-Requested-With": "XMLHttpRequest",
+ "Referer": "http://58.16.70.122/register.jsp?redirect:http://58.16.70.122.r87.com/?",
+ "Cache-Control": "no-cache",
+ "X-Scanner": "Netsparker",
+ "Cookie": "JSESSIONID=385C79E211D561C0CA13D90F150F603D",
+ "Host": "58.16.70.122",
+ "Content-Length": "0",
+ "Accept-Encoding": "gzip, deflate",
+ "__X_HTTP_URL": "58.16.70.122/disAll/tcCertType.html",
+ "__X_HTTP_RESULT_INDEX": "1"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "Apache-Coyote/1.1",
+ "Pragma": "No-cache",
+ "Expires": "Thu, 01 Jan 1970 00:00:00 GMT",
+ "Content-Type": "text/html;charset=UTF-8",
+ "Transfer-Encoding": "chunked",
+ "Date": "Sat, 18 May 2019 01:36:57 GMT",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/upload/%E6%B3%95%E5%BE%8B%E6%B3%95%E8%A7%84/%E5%B8%82%E4%BA%BA%E6%B0%91%E6%94%BF%E5%BA%9C%E5%8A%9E%E5%85%AC%E5%8E%85%E5%8D%B0%E5%8F%91%E8%B4%B5%E9%98%B3%E5%B8%82%E5%85%B3%E4%BA%8E%E6%8E%A8%E8%BF%9B%E5%B7%A5%E5%95%86%E8%90%A5%E4%B8%9A%E6%89%A7%E7%85%A7%E3%80%81%E7%BB%84%E7%BB%87%E6%9C%BA%E6%9E%84%E4%BB%A3%E7%A0%81%E8%AF%81%E5%92%8C%E7%A8%8E%E5%8A%A1%E7%99%BB%E8%AE%B0%E8%AF%81%E2%80%9C%E4%B8%89%E8%AF%81%E5%90%88%E4%B8%80%E2%80%9D%E7%99%BB%E8%AE%B0%E5%88%B6%E5%BA%A6%E6%94%B9%E9%9D%A9%E5%AE%9E%E6%96%BD%E6%96%B9%E6%A1%88%E7%9A%84%E9%80%9A%E7%9F%A5%EF%BC%88%E7%AD%91%E5%BA%9C%E5%8A%9E%E5%87%BD%E3%80%902015%E3%80%91162%E5%8F%B7%EF%BC%89.docx?nsextt=N3TSP4RKE2",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
+ "Cache-Control": "no-cache",
+ "Accept-Language": "en-us,en;q=0.5",
+ "X-Scanner": "Netsparker",
+ "Cookie": "JSESSIONID=385C79E211D561C0CA13D90F150F603D",
+ "Host": "58.16.70.122",
+ "Accept-Encoding": "gzip, deflate",
+ "__X_HTTP_URL": "58.16.70.122/upload/%E6%B3%95%E5%BE%8B%E6%B3%95%E8%A7%84/%E5%B8%82%E4%BA%BA%E6%B0%91%E6%94%BF%E5%BA%9C%E5%8A%9E%E5%85%AC%E5%8E%85%E5%8D%B0%E5%8F%91%E8%B4%B5%E9%98%B3%E5%B8%82%E5%85%B3%E4%BA%8E%E6%8E%A8%E8%BF%9B%E5%B7%A5%E5%95%86%E8%90%A5%E4%B8%9A%E6%89%A7%E7%85%A7%E3%80%81%E7%BB%84%E7%BB%87%E6%9C%BA%E6%9E%84%E4%BB%A3%E7%A0%81%E8%AF%81%E5%92%8C%E7%A8%8E%E5%8A%A1%E7%99%BB%E8%AE%B0%E8%AF%81%E2%80%9C%E4%B8%89%E8%AF%81%E5%90%88%E4%B8%80%E2%80%9D%E7%99%BB%E8%AE%B0%E5%88%B6%E5%BA%A6%E6%94%B9%E9%9D%A9%E5%AE%9E%E6%96%BD%E6%96%B9%E6%A1%88%E7%9A%84%E9%80%9A%E7%9F%A5%EF%BC%88%E7%AD%91%E5%BA%9C%E5%8A%9E%E5%87%BD%E3%80%902015%E3%80%91162%E5%8F%B7%EF%BC%89.docx?nsextt=N3TSP4RKE2",
+ "__X_HTTP_RESULT_INDEX": "3"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "Apache-Coyote/1.1",
+ "Accept-Ranges": "bytes",
+ "ETag": "W/\"1703517-1546572172000\"",
+ "Last-Modified": "Fri, 04 Jan 2019 03:22:52 GMT",
+ "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8",
+ "Content-Length": "1703517",
+ "Date": "Sat, 18 May 2019 01:37:00 GMT",
+ "__X_HTTP_RESULT_INDEX": "4"
+ }]
diff --git a/test/test_result_json/http_get_long_cookie.json b/test/test_result_json/http_get_long_cookie.json
new file mode 100644
index 0000000..8c73779
--- /dev/null
+++ b/test/test_result_json/http_get_long_cookie.json
@@ -0,0 +1,24 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "202.127.156.91.27282>14.17.32.203.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/livemsg?imagemd5=02f5efd8a349c50280f8540b2735bd54&tailroll=1&plugin=1.3.8&pf=out&si=3766845706&url=http%3A%2F%2Fsports.qq.com%2Fa%2F20160106%2F008987.htm&soid=CA7F9C5B0120568CDC2F68726300&chid=0&ping_data=dXNlcl9pbmZvPXVCWDluVDg5SFJhOUFQK0JQVGdKRUxVYi9Kdz0&t=0&iptype=0&vptag=&pid=7F993E38C0E676ACC07DE764D1F3DEF56AA8F90A&adtype=LD&oadid=6012&ev=3236&l=4020&ufc_filter=0&imagelog=1&pid2=7F993E38C0E676ACC07DE764D1F3DEF56AA8F90A&mt=15000&coverid=&reqtime=1452071981&requestl=4020&isthirdip=0&cid=0&isfloatindex=0&o=100654557&lcount=2&refluence=4020&from=0&vid=m01794rm5ej&cip=202.127.156.91&aver=0&ip_filter=0&adlength=30000&tagid=&v=TencentPlayerOutV3.2.19.346&live=0&dura=105",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "livep.l.qq.com",
+ "Connection": "keep-alive",
+ "User-Agent": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36",
+ "Accept": "*/*",
+ "Referer": "http://imgcache.qq.com/tencentvideo_v1/player/TPout.swf?max_age=86400&v=20140714",
+ "Accept-Encoding": "gzip,deflate,sdch",
+ "Accept-Language": "zh-CN,zh;q=0.8",
+ "__X_HTTP_URL": "livep.l.qq.com/livemsg?imagemd5=02f5efd8a349c50280f8540b2735bd54&tailroll=1&plugin=1.3.8&pf=out&si=3766845706&url=http%3A%2F%2Fsports.qq.com%2Fa%2F20160106%2F008987.htm&soid=CA7F9C5B0120568CDC2F68726300&chid=0&ping_data=dXNlcl9pbmZvPXVCWDluVDg5SFJhOUFQK0JQVGdKRUxVYi9Kdz0&t=0&iptype=0&vptag=&pid=7F993E38C0E676ACC07DE764D1F3DEF56AA8F90A&adtype=LD&oadid=6012&ev=3236&l=4020&ufc_filter=0&imagelog=1&pid2=7F993E38C0E676ACC07DE764D1F3DEF56AA8F90A&mt=15000&coverid=&reqtime=1452071981&requestl=4020&isthirdip=0&cid=0&isfloatindex=0&o=100654557&lcount=2&refluence=4020&from=0&vid=m01794rm5ej&cip=202.127.156.91&aver=0&ip_filter=0&adlength=30000&tagid=&v=TencentPlayerOutV3.2.19.346&live=0&dura=105",
+ "Cookie": "flashuser=95621BA8CB862E09; piao_city=179; lv_irt_id=3628e1bbe25a6c941da9fac02ec2df8b; cm_cookie=V1,10017&-EP5mRruXhQarsCl5LD-2YzgjVTvyr2K&AQEBh7uoLMUB9lnaB5Tz9XdYnGIWflXmsDrU&150723&150723,10035&7t-tEmfJ076VAsM9&AQEBh7uoLMUB9lnc4tpW7vbazqdrRdBYOUCi&150724&150807,110054&ucO0Z0gctNn3&AQEBh7uoLMUB9llxMNl45F3RAIsKK0iMOJAG&150716&151008,10040&ACZ1r0A70NaEFcGT&AQEBh7uoLMUB9lmVgSoTwuuXZi896zSVsXIF&150818&151014,110015&1&AQEBh7uoLMUB9lkt2LUHO6ARwODHLI_Y51rj&150928&151103,10037&1433388364186289251984&AQEBh7uoLMUB9llIBencOqTAEh2aQ2SURSSQ&150909&151110,10011&jL40Z03uUFI0&AQEBh7uoLMUB9lkfw2sJVNx9g12Fzs12rPSN&150717&151125,10016&F64E6LFZs0W&AQEBh7uoLMUB9llE4yoPFNUykSj7WKaRK5lH&150805&151127,10019&WQAO-C1K9qI5OP8W_t2pSw&AQEBh7uoLMUB9llhpZE87GmOk3XGo_MJgV6K&150826&151130,10015&820490997316506147&AQEBh7uoLMUB9llXiynsGYRhMO3XuPnkuHUt&150715&151201,10012&x3X1yY6b&AQEBh7uoLMUB9ll9mraU_LJCDBYsE0Sbk_V9&151202&151202,110065&ucO0Z0gctNn3&AQEBh7uoLMUB9lkJcK3KDBQTKF0YfZ5wB7r5&150716&151203,110066&jL40Z03uUFI0&AQEBh7uoLMUB9lnyvKSYhcJD1X_rSs_DLVWx&150916&151221,10013&ePyYB2MSKa0TCbebpxKjmU&AQEBh7uoLMUB9ln6_6nGNidqml4nFKXhtE58&151221&151221,110061&d9cfa518d82abee&AQEBh7uoLMUB9llj2NYzmCjxaLWXALTcAGIH&150818&151224,10038&CAESEPZbUhToZJ39CS9MlgXGUSQ&AQEBh7uoLMUB9lmhnrDM5lIGtl6vc1NxMD6F&151110&151224,10077&820490997316506147&AQEBh7uoLMUB9lmkUdUe2xSHGkvM0IRu9Jt9&151214&151228,10008&0yPSvk92ie1nhB8wTUlTq&AQEBh7uoLMUB9lnL5ZCYvXJNvlv53G0CKEkj&150817&151228,10045&0&AQEBh7uoLMUB9llW3v1Vh7W72lv14RlAjUXn&151023&151228,110064&jL40Z03uUFI0&AQEBh7uoLMUB9lkBYuCUDLDrOcGURJcilogv&151016&160104,110069&26d49ecc&AQEBh7uoLMUB9lmlBLTxQY9BkCmimkMFqTo5&151204&160105,10079&B8hGto5y1e3uDXwCMsIun3rjk--dVCof&AQEBh7uoLMUB9llxnFrhDtdNMjZ1hs1il5J4&151214&160105; LHTturn=24; ptisp=ctc; RK=hRWyd82Gd8; pgv_pvi=7567882240; image_md5=bd21d5fb2f401b37cf3a02724dc06545; LTPturn=27; pt2gguin=o0583115900; uin=o0583115900; skey=@Mp9aCinaO; ptcz=10d4b1b7bde835d64663338a8008fd4f81e2c6b5f0ba81a90da3627ee617c7ee; pgv_info=ssid=s4768939310; pgv_pvid=6872592818; o_cookie=583115900; lv_play_index_textAd=47; lv_play_indexl.=32; dc_vplaying=1; LKBturn=29; Lturn=29; adid=583115900; appuser=95621BA8CB862E09; o_minduid=phhdxyNLkxBWMa74VTm5zU4y5EbUv5vR; appuser_95621BA8CB862E09_0=2b7gwp=1453219199_6&2btemv=1455551999_1&2c8311=1453305599_3&2cfx4j=1453651199_3&2cfx9l=1453651199_1&2d49y9=1453823999_2&2d67kl=1454255999_2&2d69mf=1454255999_3&2dxv8l=1455465599_6&2dzhfl=1452614399_1&f_pogvwp=1452095999_1&f_pogvwv=1452095999_2&f_pogw0m=1452095999_1&fd_15bm2t7=1452095999_1&fd_1h2pbsd=1452095999_2&fd_1k6so62=1452095999_1&fd_rhmjmq=1452095999_2&m_roiw0t=1452095999_3&m_xty8wl=1452095999_1&pogree=1452095999_2; TX.boid=100655474=1452072582_1&701041365=1452072585_1; appuser_95621BA8CB862E09_effect_0=fd_1ez2rcc=1452095999_1&fd_qdh7zw=1452095999_1&fd_ul215j=1452095999_1; psessionid=ca7f9c5b_1452071982_583115900_30754; psessiontime=1452071990",
+ "__X_HTTP_RESULT_INDEX": "1"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_get_malformed.json b/test/test_result_json/http_get_malformed.json
new file mode 100644
index 0000000..fdaa350
--- /dev/null
+++ b/test/test_result_json/http_get_malformed.json
@@ -0,0 +1,28 @@
+ [
+ {
+ "__X_HTTP_TUPLE4": "192.168.4.2.36598>108.61.176.217.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.0",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 0,
+ "status_code": 200,
+ "Server": "FakeNet/1.3",
+ "Date": "Wed, 12 Sep 2018 19:45:55 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "1547",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+ ] \ No newline at end of file
diff --git a/test/test_result_json/http_get_multi_trans.json b/test/test_result_json/http_get_multi_trans.json
new file mode 100644
index 0000000..6bb4ca4
--- /dev/null
+++ b/test/test_result_json/http_get_multi_trans.json
@@ -0,0 +1,139 @@
+
+[{
+ "__X_HTTP_TUPLE4": "192.168.50.18.60400>192.168.42.1.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/account/login.htm",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.pro.testin.cn",
+ "Connection": "keep-alive",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept-Language": "zh-CN,zh;q=0.9",
+ "Cookie": "Hm_lvt_1b8c1194303ef64e02f003f0cb8a1906=1653898514; _gcl_au=1.1.1010551181.1653898515; _ga=GA1.2.1419569885.1653898515; _gid=GA1.2.2007113907.1653898515; authtoken_pro=tea83b3beef07488bb8571811385db42; userId_pro=1160; pid_pro=1; eid_pro=1; pname_pro=name; Hm_lpvt_1b8c1194303ef64e02f003f0cb8a1906=1653961741; JSESSIONID=531AACA879469EDAB825E28113490E10",
+ "__X_HTTP_URL": "test.pro.testin.cn/account/login.htm",
+ "__X_HTTP_RESULT_INDEX": "1"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 302,
+ "Server": "nginx/1.16.1",
+ "Date": "Tue, 31 May 2022 06:41:23 GMT",
+ "Content-Length": "0",
+ "Connection": "keep-alive",
+ "Set-Cookie": "JSESSIONID=CFAB9C0C3F4D9D6C2837E3BA9425AFCA; Path=/; HttpOnly",
+ "Set-Cookie1": "authtoken_pro=tea83b3beef07488bb8571811385db42; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Set-Cookie2": "userId_pro=1160; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Location": "http://test.pro.testin.cn/enterprise/index.htm",
+ "Content-Language": "zh-CN",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/enterprise/index.htm",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.pro.testin.cn",
+ "Connection": "keep-alive",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept-Language": "zh-CN,zh;q=0.9",
+ "Cookie": "Hm_lvt_1b8c1194303ef64e02f003f0cb8a1906=1653898514; _gcl_au=1.1.1010551181.1653898515; _ga=GA1.2.1419569885.1653898515; _gid=GA1.2.2007113907.1653898515; authtoken_pro=tea83b3beef07488bb8571811385db42; userId_pro=1160; pid_pro=1; eid_pro=1; pname_pro=name; Hm_lpvt_1b8c1194303ef64e02f003f0cb8a1906=1653961741; JSESSIONID=CFAB9C0C3F4D9D6C2837E3BA9425AFCA",
+ "__X_HTTP_URL": "test.pro.testin.cn/enterprise/index.htm",
+ "__X_HTTP_RESULT_INDEX": "3"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 302,
+ "Server": "nginx/1.16.1",
+ "Date": "Tue, 31 May 2022 06:41:23 GMT",
+ "Content-Length": "0",
+ "Connection": "keep-alive",
+ "Set-Cookie": "authtoken_pro=tea83b3beef07488bb8571811385db42; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Set-Cookie1": "userId_pro=1160; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Location": "http://test.pro.testin.cn/enterprise/into.htm?eid=1",
+ "Content-Language": "zh-CN",
+ "__X_HTTP_RESULT_INDEX": "4"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/enterprise/into.htm?eid=1",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.pro.testin.cn",
+ "Connection": "keep-alive",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept-Language": "zh-CN,zh;q=0.9",
+ "Cookie": "Hm_lvt_1b8c1194303ef64e02f003f0cb8a1906=1653898514; _gcl_au=1.1.1010551181.1653898515; _ga=GA1.2.1419569885.1653898515; _gid=GA1.2.2007113907.1653898515; authtoken_pro=tea83b3beef07488bb8571811385db42; userId_pro=1160; pid_pro=1; eid_pro=1; pname_pro=name; Hm_lpvt_1b8c1194303ef64e02f003f0cb8a1906=1653961741; JSESSIONID=CFAB9C0C3F4D9D6C2837E3BA9425AFCA",
+ "__X_HTTP_URL": "test.pro.testin.cn/enterprise/into.htm?eid=1",
+ "__X_HTTP_RESULT_INDEX": "5"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 302,
+ "Server": "nginx/1.16.1",
+ "Date": "Tue, 31 May 2022 06:41:23 GMT",
+ "Content-Length": "0",
+ "Connection": "keep-alive",
+ "Set-Cookie": "authtoken_pro=tea83b3beef07488bb8571811385db42; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Set-Cookie1": "userId_pro=1160; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Location": "http://test.pro.testin.cn/realmachine/index.htm",
+ "Content-Language": "zh-CN",
+ "__X_HTTP_RESULT_INDEX": "6"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/realmachine/index.htm",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "test.pro.testin.cn",
+ "Connection": "keep-alive",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept-Language": "zh-CN,zh;q=0.9",
+ "Cookie": "Hm_lvt_1b8c1194303ef64e02f003f0cb8a1906=1653898514; _gcl_au=1.1.1010551181.1653898515; _ga=GA1.2.1419569885.1653898515; _gid=GA1.2.2007113907.1653898515; authtoken_pro=tea83b3beef07488bb8571811385db42; userId_pro=1160; pid_pro=1; eid_pro=1; pname_pro=name; Hm_lpvt_1b8c1194303ef64e02f003f0cb8a1906=1653961741; JSESSIONID=CFAB9C0C3F4D9D6C2837E3BA9425AFCA",
+ "__X_HTTP_URL": "test.pro.testin.cn/realmachine/index.htm",
+ "__X_HTTP_RESULT_INDEX": "7"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx/1.16.1",
+ "Date": "Tue, 31 May 2022 06:41:23 GMT",
+ "Content-Type": "text/html;charset=UTF-8",
+ "Transfer-Encoding": "chunked",
+ "Connection": "keep-alive",
+ "Vary": "Accept-Encoding",
+ "Set-Cookie": "authtoken_pro=tea83b3beef07488bb8571811385db42; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Set-Cookie1": "userId_pro=1160; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Set-Cookie2": "pid_pro=1; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Set-Cookie3": "eid_pro=1; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Set-Cookie4": "pname_pro=name; Max-Age=28800; Expires=Tue, 31-May-2022 14:41:23 GMT; Domain=testin.cn; Path=/; HttpOnly",
+ "Content-Language": "zh-CN",
+ "Content-Encoding": "gzip",
+ "__X_HTTP_RESULT_INDEX": "8"
+ }] \ No newline at end of file
diff --git a/test/test_result_json/http_get_req_pipeline.json b/test/test_result_json/http_get_req_pipeline.json
new file mode 100644
index 0000000..a442274
--- /dev/null
+++ b/test/test_result_json/http_get_req_pipeline.json
@@ -0,0 +1,67 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.40.81.52802>192.168.40.137.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/aa.mp4?asf=sdaf",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "113.31.27.226",
+ "Connection": "keep-alive",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1636.2 Safari/537.36",
+ "Accept-Encoding": "gzip,deflate,sdch",
+ "Accept-Language": "zh-CN,zh;q=0.8",
+ "__X_HTTP_URL": "113.31.27.226/aa.mp4?asf=sdaf",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/fetch_ldns.png",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "ns.pb.cachecn.net",
+ "Connection": "keep-alive",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1636.2 Safari/537.36",
+ "Accept-Encoding": "gzip,deflate,sdch",
+ "Accept-Language": "zh-CN,zh;q=0.8",
+ "__X_HTTP_URL": "ns.pb.cachecn.net/fetch_ldns.png",
+ "__X_HTTP_RESULT_INDEX": "2"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/40x.jpg",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "ns.pb.cachecn.net",
+ "Connection": "keep-alive",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1636.2 Safari/537.36",
+ "Accept-Encoding": "gzip,deflate,sdch",
+ "Accept-Language": "zh-CN,zh;q=0.8",
+ "__X_HTTP_URL": "ns.pb.cachecn.net/40x.jpg",
+ "__X_HTTP_RESULT_INDEX": "3"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.0",
+ "res_status": "File not found",
+ "major_version": 1,
+ "minor_version": 0,
+ "status_code": 404,
+ "Server": "SimpleHTTP/0.6 Python/2.7.5",
+ "Date": "Wed, 25 Oct 2023 06:43:35 GMT",
+ "Content-Type": "text/html",
+ "Connection": "close",
+ "__X_HTTP_RESULT_INDEX": "4"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_get_single_trans.json b/test/test_result_json/http_get_single_trans.json
new file mode 100644
index 0000000..b227989
--- /dev/null
+++ b/test/test_result_json/http_get_single_trans.json
@@ -0,0 +1,29 @@
+[{
+ "__X_HTTP_TUPLE4": "192.168.38.73.50806>192.168.40.137.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/index.html",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "192.168.40.137",
+ "User-Agent": "curl/7.79.1",
+ "Accept": "*/*",
+ "__X_HTTP_URL": "192.168.40.137/index.html",
+ "__X_HTTP_RESULT_INDEX": "1"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.0",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 0,
+ "status_code": 200,
+ "Server": "SimpleHTTP/0.6 Python/2.7.5",
+ "Date": "Thu, 30 Nov 2023 08:42:24 GMT",
+ "Content-type": "text/html",
+ "Content-Length": "144",
+ "Last-Modified": "Thu, 30 Nov 2023 08:38:54 GMT",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }]
diff --git a/test/test_result_json/http_hdr_truncated_after_kv.json b/test/test_result_json/http_hdr_truncated_after_kv.json
new file mode 100644
index 0000000..68b5f45
--- /dev/null
+++ b/test/test_result_json/http_hdr_truncated_after_kv.json
@@ -0,0 +1,285 @@
+[{
+ "__X_HTTP_TUPLE4": "196.188.112.76.51494>23.246.50.149.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABQSwLpJi-yDFZ7k9eqarY3x3-b99vNxxOt5xDeIOapZB6Y5QXPSa54b7cbWYTXYdFimDKCeJ4s7ngqpqByvtt0aLh85nSucLTcR3-OKleuNwVltHUscQhSgVfHc.jpg?r=392",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "image/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Connection": "Keep-Alive",
+ "Host": "occ-0-778-360.1.nflxso.net",
+ "Language": "en-ET,en",
+ "Referer": "https://secure.netflix.com/us/tvui/ql/patch/20211109_18752/2/release/darwinBootstrap.js?getMainUrlFromCodex=true&taskDefaultTimeoutV2=120000&bootloader_trace=apiusernotnull__false&nq=true&nq_control_tag=tvui-main&startup_key=e38d3c70814421931b0189d91d872546326f05d8f470150d07340df9e9d72275&device_type=TCL-TVS88L&e=TCL-TVS88L0000000000000000342914&env=prod&fromNM=true&nm_prefetch=true&nrdapp_version=2015.1.1&plain=true&dh=720&dw=1280&dar=16_9&reg=undefined&authType=login&authclid=845becf0-992c-46f7-a938-1bc22b097c58&q=source_type%3D1%26additionalDataUrl%3Dhttp%253A%252F%252Flocalhost%253A56789%252Fapps%252FNetflix%252Fdial_data%26source_type_payload%3D",
+ "User-Agent": "Gibbon/2015.1.1/2015.1.1: Netflix/2015.1.1 (DEVTYPE=TCL-TVS88L; CERTVER=1)",
+ "__X_HTTP_URL": "occ-0-778-360.1.nflxso.net/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABQSwLpJi-yDFZ7k9eqarY3x3-b99vNxxOt5xDeIOapZB6Y5QXPSa54b7cbWYTXYdFimDKCeJ4s7ngqpqByvtt0aLh85nSucLTcR3-OKleuNwVltHUscQhSgVfHc.jpg?r=392",
+ "X-Gibbon-Cache-Control": "max-age=2592000, priority=4, key=/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABQSwLpJi-yDFZ7k9eqarY3x3-b99vNxxOt5xDeIOapZB6Y5QXPSa54b7cbWYTXYdFimDKCeJ4s7ngqpqByvtt0aLh85nSucLTcR3-OKleuNwVltHUscQhSgVfHc.jpg?r=392",
+ "__X_HTTP_RESULT_INDEX": "1"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABUWN8S1k3yM2coX2bwxbP699Jdr0BUqBRzIfiAJXKC5Ywt7DXqJOCjrBSYs36Tny8277IXm2BF_cgTmY18NJlocglKjhaoJhFeGoIg1cwntFduyxyRPP2EJQL5Y.jpg?r=e0e",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "image/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Connection": "Keep-Alive",
+ "Host": "occ-0-778-360.1.nflxso.net",
+ "Language": "en-ET,en",
+ "Referer": "https://secure.netflix.com/us/tvui/ql/patch/20211109_18752/2/release/darwinBootstrap.js?getMainUrlFromCodex=true&taskDefaultTimeoutV2=120000&bootloader_trace=apiusernotnull__false&nq=true&nq_control_tag=tvui-main&startup_key=e38d3c70814421931b0189d91d872546326f05d8f470150d07340df9e9d72275&device_type=TCL-TVS88L&e=TCL-TVS88L0000000000000000342914&env=prod&fromNM=true&nm_prefetch=true&nrdapp_version=2015.1.1&plain=true&dh=720&dw=1280&dar=16_9&reg=undefined&authType=login&authclid=845becf0-992c-46f7-a938-1bc22b097c58&q=source_type%3D1%26additionalDataUrl%3Dhttp%253A%252F%252Flocalhost%253A56789%252Fapps%252FNetflix%252Fdial_data%26source_type_payload%3D",
+ "User-Agent": "Gibbon/2015.1.1/2015.1.1: Netflix/2015.1.1 (DEVTYPE=TCL-TVS88L; CERTVER=1)",
+ "__X_HTTP_URL": "occ-0-778-360.1.nflxso.net/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABUWN8S1k3yM2coX2bwxbP699Jdr0BUqBRzIfiAJXKC5Ywt7DXqJOCjrBSYs36Tny8277IXm2BF_cgTmY18NJlocglKjhaoJhFeGoIg1cwntFduyxyRPP2EJQL5Y.jpg?r=e0e",
+ "X-Gibbon-Cache-Control": "max-age=2592000, priority=4, key=/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABUWN8S1k3yM2coX2bwxbP699Jdr0BUqBRzIfiAJXKC5Ywt7DXqJOCjrBSYs36Tny8277IXm2BF_cgTmY18NJlocglKjhaoJhFeGoIg1cwntFduyxyRPP2EJQL5Y.jpg?r=e0e",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABR1YQS01SaHGzoxgWBJF1Gas0Gv9_DPebb4irdCTRjcQ_FUaVbXFTTrJ68_bvJds1sb28VMq22Qn3oSSKKJ7DdLN8ybgkJooYlCD3gAntrqgIFugqv5Z3kV8rRE.jpg?r=ec7",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "image/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Connection": "Keep-Alive",
+ "Host": "occ-0-778-360.1.nflxso.net",
+ "Language": "en-ET,en",
+ "Referer": "https://secure.netflix.com/us/tvui/ql/patch/20211109_18752/2/release/darwinBootstrap.js?getMainUrlFromCodex=true&taskDefaultTimeoutV2=120000&bootloader_trace=apiusernotnull__false&nq=true&nq_control_tag=tvui-main&startup_key=e38d3c70814421931b0189d91d872546326f05d8f470150d07340df9e9d72275&device_type=TCL-TVS88L&e=TCL-TVS88L0000000000000000342914&env=prod&fromNM=true&nm_prefetch=true&nrdapp_version=2015.1.1&plain=true&dh=720&dw=1280&dar=16_9&reg=undefined&authType=login&authclid=845becf0-992c-46f7-a938-1bc22b097c58&q=source_type%3D1%26additionalDataUrl%3Dhttp%253A%252F%252Flocalhost%253A56789%252Fapps%252FNetflix%252Fdial_data%26source_type_payload%3D",
+ "__X_HTTP_URL": "occ-0-778-360.1.nflxso.net/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABR1YQS01SaHGzoxgWBJF1Gas0Gv9_DPebb4irdCTRjcQ_FUaVbXFTTrJ68_bvJds1sb28VMq22Qn3oSSKKJ7DdLN8ybgkJooYlCD3gAntrqgIFugqv5Z3kV8rRE.jpg?r=ec7",
+ "User-Agent": "Gibbon/2015.1.1/2015.1.1: Netflix/2015.1.1 (DEVTYPE=TCL-TVS88L; CERTVER=1)",
+ "X-Gibbon-Cache-Control": "max-age=2592000, priority=4, key=/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABR1YQS01SaHGzoxgWBJF1Gas0Gv9_DPebb4irdCTRjcQ_FUaVbXFTTrJ68_bvJds1sb28VMq22Qn3oSSKKJ7DdLN8ybgkJooYlCD3gAntrqgIFugqv5Z3kV8rRE.jpg?r=ec7",
+ "__X_HTTP_RESULT_INDEX": "3"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx",
+ "Date": "Sat, 13 Nov 2021 14:15:58 GMT",
+ "Content-Type": "image/jpeg",
+ "Content-Length": "18377",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=2592000",
+ "Last-Modified": "Wed, 10 Nov 2021 08:00:14 GMT",
+ "ETag": "\"c289a42885b30107ad119e2a6e405e4d\"",
+ "__X_HTTP_RESULT_INDEX": "4"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABYo5IGwDn1LaWbeaa7amS0JhH3bU5MEVlcBsC4OK0mGbea97_xoi8EbqJt8_Zp0bMuuKPE80qUUjb4wq5po_lBtulA.jpg?r=c83",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "image/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Connection": "Keep-Alive",
+ "Host": "occ-0-778-360.1.nflxso.net",
+ "Language": "en-ET,en",
+ "__X_HTTP_URL": "occ-0-778-360.1.nflxso.net/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABYo5IGwDn1LaWbeaa7amS0JhH3bU5MEVlcBsC4OK0mGbea97_xoi8EbqJt8_Zp0bMuuKPE80qUUjb4wq5po_lBtulA.jpg?r=c83",
+ "Referer": "https://secure.netflix.com/us/tvui/ql/patch/20211109_18752/2/release/darwinBootstrap.js?getMainUrlFromCodex=true&taskDefaultTimeoutV2=120000&bootloader_trace=apiusernotnull__false&nq=true&nq_control_tag=tvui-main&startup_key=e38d3c70814421931b0189d91d872546326f05d8f470150d07340df9e9d72275&device_type=TCL-TVS88L&e=TCL-TVS88L0000000000000000342914&env=prod&fromNM=true&nm_prefetch=true&nrdapp_version=2015.1.1&plain=true&dh=720&dw=1280&dar=16_9&reg=undefined&authType=login&authclid=845becf0-992c-46f7-a938-1bc22b097c58&q=source_type%3D1%26additionalDataUrl%3Dhttp%253A%252F%252Flocalhost%253A56789%252Fapps%252FNetflix%252Fdial_data%26source_type_payload%3D",
+ "User-Agent": "Gibbon/2015.1.1/2015.1.1: Netflix/2015.1.1 (DEVTYPE=TCL-TVS88L; CERTVER=1)",
+ "X-Gibbon-Cache-Control": "max-age=2592000, priority=4, key=/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABYo5IGwDn1LaWbeaa7amS0JhH3bU5MEVlcBsC4OK0mGbea97_xoi8EbqJt8_Zp0bMuuKPE80qUUjb4wq5po_lBtulA.jpg?r=c83",
+ "__X_HTTP_RESULT_INDEX": "5"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABaYF3Rmi954cg6Afu9jOtirnvF3iIMHPZCCnP34eDeYQXfRGG9Vg0qgn7hHpMVV4jOr8OZmcD2Nb7MhQv6gl-fNmVQ.jpg?r=257",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "image/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Connection": "Keep-Alive",
+ "Host": "occ-0-778-360.1.nflxso.net",
+ "Language": "en-ET,en",
+ "__X_HTTP_URL": "occ-0-778-360.1.nflxso.net/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABaYF3Rmi954cg6Afu9jOtirnvF3iIMHPZCCnP34eDeYQXfRGG9Vg0qgn7hHpMVV4jOr8OZmcD2Nb7MhQv6gl-fNmVQ.jpg?r=257",
+ "Referer": "https://secure.netflix.com/us/tvui/ql/patch/20211109_18752/2/release/darwinBootstrap.js?getMainUrlFromCodex=true&taskDefaultTimeoutV2=120000&bootloader_trace=apiusernotnull__false&nq=true&nq_control_tag=tvui-main&startup_key=e38d3c70814421931b0189d91d872546326f05d8f470150d07340df9e9d72275&device_type=TCL-TVS88L&e=TCL-TVS88L0000000000000000342914&env=prod&fromNM=true&nm_prefetch=true&nrdapp_version=2015.1.1&plain=true&dh=720&dw=1280&dar=16_9&reg=undefined&authType=login&authclid=845becf0-992c-46f7-a938-1bc22b097c58&q=source_type%3D1%26additionalDataUrl%3Dhttp%253A%252F%252Flocalhost%253A56789%252Fapps%252FNetflix%252Fdial_data%26source_type_payload%3D",
+ "User-Agent": "Gibbon/2015.1.1/2015.1.1: Netflix/2015.1.1 (DEVTYPE=TCL-TVS88L; CERTVER=1)",
+ "X-Gibbon-Cache-Control": "max-age=2592000, priority=4, key=/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABaYF3Rmi954cg6Afu9jOtirnvF3iIMHPZCCnP34eDeYQXfRGG9Vg0qgn7hHpMVV4jOr8OZmcD2Nb7MhQv6gl-fNmVQ.jpg?r=257",
+ "__X_HTTP_RESULT_INDEX": "6"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABfC-ByjFJSfWZf7MFtjVl3ONnaQ69824xWP1l-cpAFGgAfaNFk4XR9uoHNWwnbG8N2UVDctVKz0a4Uyv0mEnC2kI9Q.jpg?r=532",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "image/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Connection": "Keep-Alive",
+ "Host": "occ-0-778-360.1.nflxso.net",
+ "Language": "en-ET,en",
+ "__X_HTTP_URL": "occ-0-778-360.1.nflxso.net/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABfC-ByjFJSfWZf7MFtjVl3ONnaQ69824xWP1l-cpAFGgAfaNFk4XR9uoHNWwnbG8N2UVDctVKz0a4Uyv0mEnC2kI9Q.jpg?r=532",
+ "Referer": "https://secure.netflix.com/us/tvui/ql/patch/20211109_18752/2/release/darwinBootstrap.js?getMainUrlFromCodex=true&taskDefaultTimeoutV2=120000&bootloader_trace=apiusernotnull__false&nq=true&nq_control_tag=tvui-main&startup_key=e38d3c70814421931b0189d91d872546326f05d8f470150d07340df9e9d72275&device_type=TCL-TVS88L&e=TCL-TVS88L0000000000000000342914&env=prod&fromNM=true&nm_prefetch=true&nrdapp_version=2015.1.1&plain=true&dh=720&dw=1280&dar=16_9&reg=undefined&authType=login&authclid=845becf0-992c-46f7-a938-1bc22b097c58&q=source_type%3D1%26additionalDataUrl%3Dhttp%253A%252F%252Flocalhost%253A56789%252Fapps%252FNetflix%252Fdial_data%26source_type_payload%3D",
+ "User-Agent": "Gibbon/2015.1.1/2015.1.1: Netflix/2015.1.1 (DEVTYPE=TCL-TVS88L; CERTVER=1)",
+ "X-Gibbon-Cache-Control": "max-age=2592000, priority=4, key=/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABfC-ByjFJSfWZf7MFtjVl3ONnaQ69824xWP1l-cpAFGgAfaNFk4XR9uoHNWwnbG8N2UVDctVKz0a4Uyv0mEnC2kI9Q.jpg?r=532",
+ "__X_HTTP_RESULT_INDEX": "7"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABSyGFXaB0IqQ6VR92uKMi38mNtoz7eeWxDziAf9VYKfauhh5Qo7FnnCRb31ff6Ez9yTXsqRszsGuz0GA9FVDf_NLn-9F60IcUHm59J73eYX6BD0h4wLLK0Da6YM.jpg?r=aaa",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "image/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Connection": "Keep-Alive",
+ "Host": "occ-0-778-360.1.nflxso.net",
+ "Language": "en-ET,en",
+ "__X_HTTP_URL": "occ-0-778-360.1.nflxso.net/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABSyGFXaB0IqQ6VR92uKMi38mNtoz7eeWxDziAf9VYKfauhh5Qo7FnnCRb31ff6Ez9yTXsqRszsGuz0GA9FVDf_NLn-9F60IcUHm59J73eYX6BD0h4wLLK0Da6YM.jpg?r=aaa",
+ "Referer": "https://secure.netflix.com/us/tvui/ql/patch/20211109_18752/2/release/darwinBootstrap.js?getMainUrlFromCodex=true&taskDefaultTimeoutV2=120000&bootloader_trace=apiusernotnull__false&nq=true&nq_control_tag=tvui-main&startup_key=e38d3c70814421931b0189d91d872546326f05d8f470150d07340df9e9d72275&device_type=TCL-TVS88L&e=TCL-TVS88L0000000000000000342914&env=prod&fromNM=true&nm_prefetch=true&nrdapp_version=2015.1.1&plain=true&dh=720&dw=1280&dar=16_9&reg=undefined&authType=login&authclid=845becf0-992c-46f7-a938-1bc22b097c58&q=source_type%3D1%26additionalDataUrl%3Dhttp%253A%252F%252Flocalhost%253A56789%252Fapps%252FNetflix%252Fdial_data%26source_type_payload%3D",
+ "User-Agent": "Gibbon/2015.1.1/2015.1.1: Netflix/2015.1.1 (DEVTYPE=TCL-TVS88L; CERTVER=1)",
+ "X-Gibbon-Cache-Control": "max-age=2592000, priority=4, key=/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABSyGFXaB0IqQ6VR92uKMi38mNtoz7eeWxDziAf9VYKfauhh5Qo7FnnCRb31ff6Ez9yTXsqRszsGuz0GA9FVDf_NLn-9F60IcUHm59J73eYX6BD0h4wLLK0Da6YM.jpg?r=aaa",
+ "__X_HTTP_RESULT_INDEX": "8"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABZbHXQr7bRUpSQ2vQe8F8p3xODTJjUbSjEcLgQrFVyGsPgQT1GVhqGWFetJhebcGrVeZGOTmQ3qvHTe9eBRJdjFsVg.jpg?r=723",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "image/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Connection": "Keep-Alive",
+ "Host": "occ-0-778-360.1.nflxso.net",
+ "Language": "en-ET,en",
+ "__X_HTTP_URL": "occ-0-778-360.1.nflxso.net/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABZbHXQr7bRUpSQ2vQe8F8p3xODTJjUbSjEcLgQrFVyGsPgQT1GVhqGWFetJhebcGrVeZGOTmQ3qvHTe9eBRJdjFsVg.jpg?r=723",
+ "Referer": "https://secure.netflix.com/us/tvui/ql/patch/20211109_18752/2/release/darwinBootstrap.js?getMainUrlFromCodex=true&taskDefaultTimeoutV2=120000&bootloader_trace=apiusernotnull__false&nq=true&nq_control_tag=tvui-main&startup_key=e38d3c70814421931b0189d91d872546326f05d8f470150d07340df9e9d72275&device_type=TCL-TVS88L&e=TCL-TVS88L0000000000000000342914&env=prod&fromNM=true&nm_prefetch=true&nrdapp_version=2015.1.1&plain=true&dh=720&dw=1280&dar=16_9&reg=undefined&authType=login&authclid=845becf0-992c-46f7-a938-1bc22b097c58&q=source_type%3D1%26additionalDataUrl%3Dhttp%253A%252F%252Flocalhost%253A56789%252Fapps%252FNetflix%252Fdial_data%26source_type_payload%3D",
+ "User-Agent": "Gibbon/2015.1.1/2015.1.1: Netflix/2015.1.1 (DEVTYPE=TCL-TVS88L; CERTVER=1)",
+ "X-Gibbon-Cache-Control": "max-age=2592000, priority=4, key=/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABZbHXQr7bRUpSQ2vQe8F8p3xODTJjUbSjEcLgQrFVyGsPgQT1GVhqGWFetJhebcGrVeZGOTmQ3qvHTe9eBRJdjFsVg.jpg?r=723",
+ "__X_HTTP_RESULT_INDEX": "9"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx",
+ "Date": "Sat, 13 Nov 2021 14:15:58 GMT",
+ "Content-Type": "image/jpeg",
+ "Content-Length": "13488",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=2592000",
+ "Last-Modified": "Mon, 08 Nov 2021 19:50:54 GMT",
+ "ETag": "\"35d56b6a0ef3b5857013f44620bd8888\"",
+ "__X_HTTP_RESULT_INDEX": "10"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx",
+ "Date": "Sat, 13 Nov 2021 14:15:58 GMT",
+ "Content-Type": "image/jpeg",
+ "Content-Length": "14129",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=2592000",
+ "Last-Modified": "Tue, 26 Oct 2021 15:12:58 GMT",
+ "ETag": "\"bb83961ad5fe366dcbb5240ead69f650\"",
+ "__X_HTTP_RESULT_INDEX": "11"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx",
+ "Date": "Sat, 13 Nov 2021 14:15:58 GMT",
+ "Content-Type": "image/jpeg",
+ "Content-Length": "11493",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=2592000",
+ "Last-Modified": "Thu, 04 Nov 2021 20:44:22 GMT",
+ "ETag": "\"c5be6a7137482da270bb2adc8d44a28d\"",
+ "__X_HTTP_RESULT_INDEX": "12"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx",
+ "Date": "Sat, 13 Nov 2021 14:15:58 GMT",
+ "Content-Type": "image/jpeg",
+ "Content-Length": "14219",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=2592000",
+ "Last-Modified": "Sat, 25 Sep 2021 05:02:49 GMT",
+ "ETag": "\"61bec96775876749bb57139f86f6b0ca\"",
+ "__X_HTTP_RESULT_INDEX": "13"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx",
+ "Date": "Sat, 13 Nov 2021 14:15:58 GMT",
+ "Content-Type": "image/jpeg",
+ "Content-Length": "21967",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=2592000",
+ "Last-Modified": "Fri, 02 Jul 2021 10:15:04 GMT",
+ "ETag": "\"841065529f1e89eabd0ef235a3d3291c\"",
+ "__X_HTTP_RESULT_INDEX": "14"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx",
+ "Date": "Sat, 13 Nov 2021 14:15:58 GMT",
+ "Content-Type": "image/jpeg",
+ "Content-Length": "21670",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=2592000",
+ "Last-Modified": "Tue, 19 Oct 2021 14:00:02 GMT",
+ "ETag": "\"21d4b2c21a3a2f26ba665670e8513940\"",
+ "__X_HTTP_RESULT_INDEX": "15"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABSFXjTUaVrddq_nehO4yuLziSjuekxJuv3oEsyUpmt3oK3InJcXjtZUHrBBuu0EP05WRC8wFVe78VtxtW_ZuZQ65MIcApb0oZF4JoFlHHnv363RbgJn898Q4tQc.jpg?r=590",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Accept": "image/*",
+ "Accept-Encoding": "deflate, gzip",
+ "Connection": "Keep-Alive",
+ "Host": "occ-0-778-360.1.nflxso.net",
+ "Language": "en-ET,en",
+ "Referer": "https://secure.netflix.com/us/tvui/ql/patch/20211109_18752/2/release/darwinBootstrap.js?getMainUrlFromCodex=true&taskDefaultTimeoutV2=120000&bootloader_trace=apiusernotnull__false&nq=true&nq_control_tag=tvui-main&startup_key=e38d3c70814421931b0189d91d872546326f05d8f470150d07340df9e9d72275&device_type=TCL-TVS88L&e=TCL-TVS88L0000000000000000342914&env=prod&fromNM=true&nm_prefetch=true&nrdapp_version=2015.1.1&plain=true&dh=720&dw=1280&dar=16_9&reg=undefined&authType=login&authclid=845becf0-992c-46f7-a938-1bc22b097c58&q=source_type%3D1%26additionalDataUrl%3Dhttp%253A%252F%252Flocalhost%253A56789%252Fapps%252FNetflix%252Fdial_data%26source_type_payload%3D",
+ "User-Agent": "Gibbon/2015.1.1/2015.1.1: Netflix/2015.1.1 (DEVTYPE=TCL-TVS88L; CERTVER=1)",
+ "__X_HTTP_URL": "occ-0-778-360.1.nflxso.net/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABSFXjTUaVrddq_nehO4yuLziSjuekxJuv3oEsyUpmt3oK3InJcXjtZUHrBBuu0EP05WRC8wFVe78VtxtW_ZuZQ65MIcApb0oZF4JoFlHHnv363RbgJn898Q4tQc.jpg?r=590",
+ "X-Gibbon-Cache-Control": "max-age=2592000, priority=4, key=/dnm/api/v6/Da_vleYcahiCE7JMYt8LJRyoenc/AAAABSFXjTUaVrddq_nehO4yuLziSjuekxJuv3oEsyUpmt3oK3InJcXjtZUHrBBuu0EP05WRC8wFVe78VtxtW_ZuZQ65MIcApb0oZF4JoFlHHnv363RbgJn898Q4tQc.jpg?r=590",
+ "__X_HTTP_RESULT_INDEX": "16"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx",
+ "Date": "Sat, 13 Nov 2021 14:15:58 GMT",
+ "Content-Type": "image/jpeg",
+ "Content-Length": "14215",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=2592000",
+ "Last-Modified": "Thu, 11 Nov 2021 17:32:01 GMT",
+ "ETag": "\"837a2051f7d0f2899baf54ff20b3d9ad\"",
+ "__X_HTTP_RESULT_INDEX": "17"
+ }]
diff --git a/test/test_result_json/http_hdr_truncated_in_kv.json b/test/test_result_json/http_hdr_truncated_in_kv.json
new file mode 100644
index 0000000..f123f6d
--- /dev/null
+++ b/test/test_result_json/http_hdr_truncated_in_kv.json
@@ -0,0 +1,53 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "196.190.248.93.32727>94.130.141.49.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/iframes2/e38f4959d33f4fa390045b0d7123997d.html?subid=65843620",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "tsyndicate.com",
+ "Connection": "keep-alive",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Mozilla/5.0 (Linux; Android 11; SM-A217F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.74 Mobile Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
+ "Referer": "http://the-sexy-tube.com/",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
+ "__X_HTTP_URL": "tsyndicate.com/iframes2/e38f4959d33f4fa390045b0d7123997d.html?subid=65843620",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "nginx",
+ "Date": "Sat, 13 Nov 2021 13:58:01 GMT",
+ "Content-Type": "text/html; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Connection": "keep-alive",
+ "Vary": "Accept-Encoding",
+ "Cache-Control": "no-cache, no-store, no-transform, must-revalidate",
+ "Pragma": "no-cache",
+ "Expires": "0",
+ "Vary1": "*",
+ "X-Api-Version": "2",
+ "Link": "<http://lcdn.tsyndicate.com/sdk/v1/b.b.js>; rel=preload; as=script",
+ "X-Request-Id": "2ceed9968bf8c648",
+ "Set-Cookie": "ts_uid=6ec96511-9fa6-4e10-86b8-31fdb4531864; expires=Fri, 13 May 2022 13:58:01 GMT; domain=.tsyndicate.com; path=/; HttpOnly; secure; SameSite=None",
+ "Set-Cookie2": "bfq=e0SIEaFjiwwZNWjkkDGjCwsRYwpuifFQRJmJMWzMsIEjBw4ZOCr2URAQ; expires=Sun, 14 Nov 2021 13:58:01 GMT; domain=.tsyndicate.com; path=/; secure; SameSite=None",
+ "X-Robots-Tag": "none",
+ "Cache-Control3": "no-transform",
+ "X-Robots-Tag4": "noindex, nofollow",
+ "Report-To": "{ \"url\": \"https://pxl.tsyndicate.com/api/v1/heavy-ad/report\", \"max_age\": 86401 }",
+ "Content-Encoding": "gzip",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_hdr_value_empty.json b/test/test_result_json/http_hdr_value_empty.json
new file mode 100644
index 0000000..aface09
--- /dev/null
+++ b/test/test_result_json/http_hdr_value_empty.json
@@ -0,0 +1,35 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.131.33.47164>192.168.204.67.4445",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "POST",
+ "uri": "http://:4445/RPC2",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "User-Agent": "ulxmlrpcpp/1.7.5",
+ "Connection": "Close",
+ "Content-Type": "text/xml",
+ "Date": "Sat Sep 7 10:04:57 2019",
+ "Content-Length": "468",
+ "__X_HTTP_URL": "http://:4445/RPC2",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Connection": "Close",
+ "Content-Type": "text/xml",
+ "Transfer-Encoding": "chunked",
+ "X-Powered-By": "ulxmlrpcpp/1.7.4",
+ "Date": "Sat Sep 7 01:09:08 2019",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_hdrs_exceed_maximum.json b/test/test_result_json/http_hdrs_exceed_maximum.json
new file mode 100644
index 0000000..09b7311
--- /dev/null
+++ b/test/test_result_json/http_hdrs_exceed_maximum.json
@@ -0,0 +1,46 @@
+[{
+ "__X_HTTP_TUPLE4": "10.0.0.1.61462>10.0.0.2.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/x/xx/xxxxxxxxxxxxxxxxxxx/x/xxxxxx/xxxxxxxxxxxxxxx?xxx=1&xxx=1&x=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&vmf=xxxxxxxxxx.xxx.xxx.xxx&ce=UTF-8&ns=xxxxxxxxxx&pageName=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&g=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.jsp&r=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&events=xxxxxxxxxxxxxxxxxxxxxxxxxxx&products=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&v1=xxxxxxxxxxxxxxx&v2=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&v17=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&c49=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&AQE=1",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "xxxxx.xxxxxxxx.xxxxxxxxxx.xxx",
+ "Connection": "keep-alive",
+ "Accept": "image/webp,*/*;q=0.8",
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
+ "Referer": "http://www.xxxxxxxxxx.xxx/xx/xxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxx.jsp",
+ "Accept-Encoding": "gzip,deflate,sdch",
+ "Accept-Language": "en-US,en;q=0.8,en-GB;q=0.6",
+ "__X_HTTP_URL": "xxxxx.xxxxxxxx.xxxxxxxxxx.xxx/x/xx/xxxxxxxxxxxxxxxxxxx/x/xxxxxx/xxxxxxxxxxxxxxx?xxx=1&xxx=1&x=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&vmf=xxxxxxxxxx.xxx.xxx.xxx&ce=UTF-8&ns=xxxxxxxxxx&pageName=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&g=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.jsp&r=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&events=xxxxxxxxxxxxxxxxxxxxxxxxxxx&products=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&v1=xxxxxxxxxxxxxxx&v2=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&v17=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&c49=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&AQE=1",
+ "Cookie": "xxxxxxxxxxxxxxxxxxx=ie; xxxxxxxxxxxxxxxxxxxxxx=true; lp=xxxxxx; rememberUn=false; xxx.xxxxxxxxxx.xxxxxxxxxx=xx; xxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; autocomplete=1; xxxx=xxxx; xxxx=xxxxv1|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; xxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "__X_HTTP_RESULT_INDEX": "1"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Date": "Mon, 30 Jun 2014 13:35:21 GMT",
+ "Server": "xxxxxxxxxxxxxxxxx",
+ "Access-Control-Allow-Origin": "*",
+ "Set-Cookie": "xxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; Expires=Wed, 29 Jun 2016 13:35:21 GMT; Domain=.xxxxxxxxxx.xxx; Path=/",
+ "X-C": "ms-4.9",
+ "Expires": "Sun, 29 Jun 2014 13:35:21 GMT",
+ "Last-Modified": "Tue, 01 Jul 2014 13:35:21 GMT",
+ "Cache-Control": "no-cache, no-store, max-age=0, no-transform, private",
+ "Pragma": "no-cache",
+ "ETag": "\"xxxxxxxxxxxxxxxxxxxxxx\"",
+ "Vary": "*",
+ "P3P": "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID PSA OUR IND COM NAV STA\"",
+ "xserver": "xxxxxx",
+ "Content-Length": "43",
+ "Keep-Alive": "timeout=15",
+ "Connection": "Keep-Alive",
+ "Content-Type": "image/gif",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }]
diff --git a/test/test_result_json/http_multi_parse_error.json b/test/test_result_json/http_multi_parse_error.json
new file mode 100644
index 0000000..8111a6f
--- /dev/null
+++ b/test/test_result_json/http_multi_parse_error.json
@@ -0,0 +1,35 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.131.33.47172>192.168.204.67.4445",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "POST",
+ "uri": "http://:4445/RPC2",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "User-Agent": "ulxmlrpcpp/1.7.5",
+ "Connection": "Close",
+ "Content-Type": "text/xml",
+ "Date": "Sat Sep 7 10:05:13 2019",
+ "Content-Length": "468",
+ "__X_HTTP_URL": "http://:4445/RPC2",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Connection": "Close",
+ "Content-Type": "text/xml",
+ "Transfer-Encoding": "chunked",
+ "X-Powered-By": "ulxmlrpcpp/1.7.4",
+ "Date": "Sat Sep 7 01:09:24 2019",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_no_content_length.json b/test/test_result_json/http_no_content_length.json
new file mode 100644
index 0000000..93ca066
--- /dev/null
+++ b/test/test_result_json/http_no_content_length.json
@@ -0,0 +1,45 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "10.0.0.1.50384>10.0.0.2.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/js/xxxxxx.js",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "xxxxxxx.xxxxxx.xx",
+ "User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3",
+ "Accept": "*/*",
+ "Accept-Language": "en-us,en;q=0.5",
+ "Accept-Encoding": "gzip,deflate",
+ "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+ "Keep-Alive": "115",
+ "Connection": "keep-alive",
+ "Referer": "http://www.xxxxxxxx.com/xxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx.html",
+ "Cookie": "trafic_ranking=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "__X_HTTP_URL": "xxxxxxx.xxxxxx.xx/js/xxxxxx.js",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.0",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 0,
+ "status_code": 200,
+ "Date": "Mon, 10 May 2010 08:31:02 GMT",
+ "Server": "Apache",
+ "Content-type": "application/x-javascript",
+ "Expires": "Thu, 11 Jan 1973 16:00:00 GMT",
+ "Last-Modified": "Mon, 10 May 2010 08:31:02 GMT",
+ "Cache-Control": "no-store, no-cache, must-revalidate, post-check=0, pre-check=0",
+ "Pragma": "no-cache",
+ "P3P": "policyref=\"/w3c/p3p.xml\", CP=\"ALL IND DSP COR ADM CONo CUR IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI\"",
+ "Set-Cookie": "trafic_ranking=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; expires=Sun, 11-Jan-2037 14:00:00 GMT; path=/; domain=.xxxxxx.xx",
+ "connection": "close",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_over_pppoe.json b/test/test_result_json/http_over_pppoe.json
new file mode 100644
index 0000000..23856ca
--- /dev/null
+++ b/test/test_result_json/http_over_pppoe.json
@@ -0,0 +1,35 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "2a00:5e80:101:212d:504:7b1:2572:db22.37034>2606:f200:0:7:bad:f00d:d00d:1.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "User-Agent": "curl/7.34.0",
+ "Host": "ipv6.icanhazip.com",
+ "Accept": "*/*",
+ "__X_HTTP_URL": "ipv6.icanhazip.com/",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Date": "Thu, 02 Jan 2014 08:38:06 GMT",
+ "Server": "Apache",
+ "Content-Length": "38",
+ "Content-Type": "text/plain; charset=UTF-8",
+ "X-RTFM": "Learn about this site at http://bit.ly/14DAh2o and don't abuse the service",
+ "X-YOU-SHOULD-APPLY-FOR-A-JOB": "If you're reading this, apply here: http://rackertalent.com/",
+ "X-ICANHAZNODE": "icanhazip1.nugget",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_over_tcp_keepalive.json b/test/test_result_json/http_over_tcp_keepalive.json
new file mode 100644
index 0000000..1481cb7
--- /dev/null
+++ b/test/test_result_json/http_over_tcp_keepalive.json
@@ -0,0 +1,40 @@
+[{
+ "__X_HTTP_TUPLE4": "192.168.56.66.55356>60.190.243.167.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ }, {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "www.yumi.com",
+ "Connection": "keep-alive",
+ "Cache-Control": "max-age=0",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept-Language": "zh-CN,zh;q=0.9",
+ "Cookie": "UM_distinctid=17d37645f9c1a1-0281befa480414-b7a1a38-144000-17d37645f9d336; CNZZDATA1258295942=1778021578-1637307701-%7C1637307701; Hm_lvt_a6dc86f6e27435039966e994bd7f0792=1637311872; yumi_sid=JaMlFyTA07ikpZjTHZsRTWyGdMqFyFy%2B4hXGj%2FSoQRJYbrfBUQuOTIMZ8jUGmugDC594AYcbeRhg75xidhRxCW4zq9Y0gPwTmkhq4LQuprp4DrtDMLI3L5wLMqkG%2FuAX1aVFPfud5GRNxNFTSp%2Bos%2FKhfCFKhfN5%2BuT2xyVYSAjy2ftiSOGDi7FN13icuuyPhFCoWqOxWVu1CZ3AiYPJssv6kXqiR6paf75icdeROZY2bkFCDKkcIQcPy7o9EKpkL1Mbimeb40JMg9hUsWdmyhDkzVjSHJmC4z2ujpzSDTsjRIQOnxTy1PHZi%2FMwg3uyGLCusDwqbagpO4pcgEJ5ONDy%2BGwO7FmHXU3mFfR56c9HxxiiuLPnBt9ErpqqWKsxH6lUrlHaUp6AzyrgX7PFdksiMfPSk6%2F3%2FWOYr%2FkYuI4fopw7z8%2FLhxC9AiLr9Czz3MngFUGzkmaMVvAhZOSPzg%3D%3D; Hm_lpvt_a6dc86f6e27435039966e994bd7f0792=1637313847",
+ "__X_HTTP_URL": "www.yumi.com/",
+ "__X_HTTP_RESULT_INDEX": "1"
+ }, {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "Tengine",
+ "Date": "Fri, 19 Nov 2021 09:57:40 GMT",
+ "Content-Type": "text/html",
+ "Transfer-Encoding": "chunked",
+ "Connection": "keep-alive",
+ "Vary": "Accept-Encoding",
+ "Set-Cookie": "yumi_sid=V6EhlyovLp46BBzQWLVOBg%2F73RUD5E%2FfaRlkR8RLa8aKhGrPVdVHvHfFWRKKd9wZ%2BfFF4Tb2wnVOOn%2F9iXbBpyHsbxjmUqnbFSoAX7QIJjt%2BEEjAL3M7O7VSpAyMnrFKt7qu46oXV%2B6teyyTUY7Ucy285v6otvZcu8bN%2B5YxKZ1gYh56iJ0bHxnrnQ0vvAx3l%2BLwfw2y0c5IaF2tjrL%2Fn83nrHsPoYYRWAR2zLIXD%2FEMKRtyerwsM5LKhZZteFGWD2w%2B15alKF5T65i0lPvPcAdaqpceL5xz23twQULhs1tIJsOfJZ8JudLlRy6x3DvxQYqRe2xTCex5c77zJqfq%2FdryNbBycIq9gf6C2hXDRwDqRqVgXDMadwGnooKFkv%2ByCbohjHyBCZJypBcYFmglYhin23UC9i%2B%2BOA%2FxhlxcnU8kT8udpTNCktSmF950SQLOmvdvYuXGydKs8v05cxe5fg%3D%3D; expires=Fri, 19-Nov-2021 11:57:38 GMT; Max-Age=7200; path=/; domain=.yumi.com",
+ "Pragma": "no-cache",
+ "Cache-Control": "no-store",
+ "Content-Encoding": "gzip",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }]
diff --git a/test/test_result_json/http_over_tls.json b/test/test_result_json/http_over_tls.json
new file mode 100644
index 0000000..32960f8
--- /dev/null
+++ b/test/test_result_json/http_over_tls.json
@@ -0,0 +1,2 @@
+[
+] \ No newline at end of file
diff --git a/test/test_result_json/http_post_multipart_form_data.json b/test/test_result_json/http_post_multipart_form_data.json
new file mode 100644
index 0000000..3e761dd
--- /dev/null
+++ b/test/test_result_json/http_post_multipart_form_data.json
@@ -0,0 +1,101 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.8.97.11371>192.168.57.14.8080",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/fileupload/",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "192.168.57.14:8080",
+ "Connection": "keep-alive",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "User-Agent": "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36",
+ "Accept-Encoding": "gzip,deflate,sdch",
+ "Accept-Language": "zh-CN,zh;q=0.8",
+ "Cookie": "JSESSIONID=969AC5FBD069EE6218EB10513726B244; JSESSIONID=400CC78DF5784F303702CC7F02C6122C",
+ "__X_HTTP_URL": "192.168.57.14:8080/fileupload/",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "Apache-Coyote/1.1",
+ "Content-Type": "text/html;charset=UTF-8",
+ "Content-Length": "468",
+ "Date": "Thu, 28 Mar 2019 08:13:33 GMT",
+ "__X_HTTP_RESULT_INDEX": "2"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/fileupload/",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "192.168.57.14:8080",
+ "Connection": "keep-alive",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "User-Agent": "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36",
+ "Accept-Encoding": "gzip,deflate,sdch",
+ "Accept-Language": "zh-CN,zh;q=0.8",
+ "Cookie": "JSESSIONID=969AC5FBD069EE6218EB10513726B244; JSESSIONID=400CC78DF5784F303702CC7F02C6122C",
+ "__X_HTTP_URL": "192.168.57.14:8080/fileupload/",
+ "__X_HTTP_RESULT_INDEX": "3"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "Apache-Coyote/1.1",
+ "Content-Type": "text/html;charset=UTF-8",
+ "Content-Length": "468",
+ "Date": "Thu, 28 Mar 2019 08:13:33 GMT",
+ "__X_HTTP_RESULT_INDEX": "4"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "POST",
+ "uri": "/fileupload/servlet/UploadHandleServlet",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "192.168.57.14:8080",
+ "Connection": "keep-alive",
+ "Content-Length": "449",
+ "Cache-Control": "max-age=0",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "Origin": "http://192.168.57.14:8080",
+ "User-Agent": "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36",
+ "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryAe47vGj7ybAe6RwO",
+ "Referer": "http://192.168.57.14:8080/fileupload/",
+ "Accept-Encoding": "gzip,deflate,sdch",
+ "Accept-Language": "zh-CN,zh;q=0.8",
+ "Cookie": "JSESSIONID=969AC5FBD069EE6218EB10513726B244; JSESSIONID=400CC78DF5784F303702CC7F02C6122C",
+ "__X_HTTP_URL": "192.168.57.14:8080/fileupload/servlet/UploadHandleServlet",
+ "__X_HTTP_RESULT_INDEX": "5"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Server": "Apache-Coyote/1.1",
+ "Content-Type": "text/html;charset=UTF-8",
+ "Content-Length": "144",
+ "Date": "Thu, 28 Mar 2019 08:13:37 GMT",
+ "__X_HTTP_RESULT_INDEX": "6"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_req_1byte_sliding_window.json b/test/test_result_json/http_req_1byte_sliding_window.json
new file mode 100644
index 0000000..c295389
--- /dev/null
+++ b/test/test_result_json/http_req_1byte_sliding_window.json
@@ -0,0 +1,34 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.40.137.46180>192.168.42.40.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/index.html",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "User-Agent": "Wget/1.14 (linux-gnu)",
+ "Accept": "*/*",
+ "Host": "192.168.42.40",
+ "__X_HTTP_URL": "192.168.42.40/index.html",
+ "Connection": "Keep-Alive",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.0",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 0,
+ "status_code": 200,
+ "Server": "SimpleHTTP/0.6 Python/2.7.5",
+ "Date": "Fri, 29 Dec 2023 09:11:12 GMT",
+ "Content-type": "text/html",
+ "Content-Length": "144",
+ "Last-Modified": "Fri, 29 Dec 2023 08:50:53 GMT",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_res_1byte_sliding_window.json b/test/test_result_json/http_res_1byte_sliding_window.json
new file mode 100644
index 0000000..60b756d
--- /dev/null
+++ b/test/test_result_json/http_res_1byte_sliding_window.json
@@ -0,0 +1,34 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.42.40.36338>192.168.40.137.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/index.html",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "User-Agent": "Wget/1.14 (linux-gnu)",
+ "Accept": "*/*",
+ "Host": "192.168.40.137",
+ "Connection": "Keep-Alive",
+ "__X_HTTP_URL": "192.168.40.137/index.html",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.0",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 0,
+ "status_code": 200,
+ "Server": "SimpleHTTP/0.6 Python/2.7.5",
+ "Date": "Fri, 29 Dec 2023 09:32:21 GMT",
+ "Content-type": "text/html",
+ "Content-Length": "144",
+ "Last-Modified": "Fri, 29 Dec 2023 08:50:53 GMT",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_res_gzip.json b/test/test_result_json/http_res_gzip.json
new file mode 100644
index 0000000..357dbe9
--- /dev/null
+++ b/test/test_result_json/http_res_gzip.json
@@ -0,0 +1,44 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.69.2.34059>192.168.69.1.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/test/ethereal.html",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "cerberus",
+ "User-Agent": "Mozilla/5.0 (X11; U; Linux ppc; rv:1.7.3) Gecko/20041004 Firefox/0.10.1",
+ "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
+ "Accept-Language": "en-us,en;q=0.5",
+ "Accept-Encoding": "gzip,deflate",
+ "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+ "Keep-Alive": "300",
+ "Connection": "keep-alive",
+ "Cookie": "FGNCLIID=05c04axp1yaqynldtcdiwis0ag1",
+ "__X_HTTP_URL": "cerberus/test/ethereal.html",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Date": "Fri, 29 Oct 2004 05:21:00 GMT",
+ "Server": "Apache/2.0.50 (Fedora)",
+ "Last-Modified": "Fri, 29 Oct 2004 05:20:21 GMT",
+ "ETag": "\"126e1f-6d-371b2f40\"",
+ "Accept-Ranges": "bytes",
+ "Vary": "Accept-Encoding",
+ "Content-Encoding": "gzip",
+ "Content-Length": "92",
+ "Connection": "close",
+ "Content-Type": "text/html; charset=UTF-8",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_trans_pipeline.json b/test/test_result_json/http_trans_pipeline.json
new file mode 100644
index 0000000..c9b5d37
--- /dev/null
+++ b/test/test_result_json/http_trans_pipeline.json
@@ -0,0 +1,368 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "223.72.39.14.2545>192.168.182.147.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/postinfo.html",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/postinfo.html",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_bin/_vti_aut/author.dll",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_bin/_vti_aut/author.dll",
+ "__X_HTTP_RESULT_INDEX": "2"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_bin/_vti_aut/author.exe",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_bin/_vti_aut/author.exe",
+ "__X_HTTP_RESULT_INDEX": "3"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_bin/_vti_aut/dvwssr.dll",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_bin/_vti_aut/dvwssr.dll",
+ "__X_HTTP_RESULT_INDEX": "4"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_bin/_vti_adm/admin.dll",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_bin/_vti_adm/admin.dll",
+ "__X_HTTP_RESULT_INDEX": "5"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_bin/_vti_adm/admin.exe",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_bin/_vti_adm/admin.exe",
+ "__X_HTTP_RESULT_INDEX": "6"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_bin/fpcount.exe?Page=default.asp|Image=3",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_bin/fpcount.exe?Page=default.asp|Image=3",
+ "__X_HTTP_RESULT_INDEX": "7"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:01 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "8"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:02 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "9"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:03 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "10"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:04 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "11"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:05 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "12"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_bin/shtml.dll",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "__X_HTTP_URL": "116.181.2.152/_vti_bin/shtml.dll",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "13"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_bin/shtml.exe",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_bin/shtml.exe",
+ "__X_HTTP_RESULT_INDEX": "14"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_pvt/_x_todo.htm",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_pvt/_x_todo.htm",
+ "__X_HTTP_RESULT_INDEX": "15"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_pvt/_x_todoh.htm",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_pvt/_x_todoh.htm",
+ "__X_HTTP_RESULT_INDEX": "16"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_pvt/access.cnf",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_pvt/access.cnf",
+ "__X_HTTP_RESULT_INDEX": "17"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_pvt/administrator.pwd",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_pvt/administrator.pwd",
+ "__X_HTTP_RESULT_INDEX": "18"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_pvt/administrators.pwd",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_pvt/administrators.pwd",
+ "__X_HTTP_RESULT_INDEX": "19"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_pvt/authors.pwd",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_pvt/authors.pwd",
+ "__X_HTTP_RESULT_INDEX": "20"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:06 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "21"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:07 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "22"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:08 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "23"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:09 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "24"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:10 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "25"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/_vti_pvt/bots.cnf",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "116.181.2.152",
+ "User-Agent": "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)",
+ "Connection": "keep-alive",
+ "__X_HTTP_URL": "116.181.2.152/_vti_pvt/bots.cnf",
+ "__X_HTTP_RESULT_INDEX": "26"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Not Found",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 404,
+ "Server": "nginx",
+ "Date": "Thu, 29 Oct 2020 09:59:11 GMT",
+ "Content-Type": "text/html",
+ "Content-Length": "146",
+ "Connection": "keep-alive",
+ "__X_HTTP_RESULT_INDEX": "27"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_tunnel_for_pop3.json b/test/test_result_json/http_tunnel_for_pop3.json
new file mode 100644
index 0000000..7956a50
--- /dev/null
+++ b/test/test_result_json/http_tunnel_for_pop3.json
@@ -0,0 +1,33 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.10.58.51798>192.168.10.144.808",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "CONNECT",
+ "uri": "pop.163.com:110",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Content-Length": "0",
+ "Accept": "*/*",
+ "User-Agent": "Foxmail 7, 1, 3, 48[cn]",
+ "Accept-Encoding": "gzip, deflate",
+ "Proxy-Connection": "Keep-Alive",
+ "Connection": "Keep-Alive",
+ "Host": "192.168.10.144",
+ "__X_HTTP_URL": "192.168.10.144pop.163.com:110",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Connection established",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 200,
+ "Proxy-agent": "CCProxy",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_upgrade_http2.json b/test/test_result_json/http_upgrade_http2.json
new file mode 100644
index 0000000..3d455a5
--- /dev/null
+++ b/test/test_result_json/http_upgrade_http2.json
@@ -0,0 +1,42 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "10.9.0.2.58038>139.162.123.134.80",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/robots.txt",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "nghttp2.org",
+ "User-Agent": "curl/7.61.0",
+ "Accept": "*/*",
+ "Connection": "Upgrade, HTTP2-Settings",
+ "Upgrade": "h2c",
+ "HTTP2-Settings": "AAMAAABkAARAAAAAAAIAAAAA",
+ "__X_HTTP_URL": "nghttp2.org/robots.txt",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "PRI",
+ "uri": "*",
+ "req_version": "2.0",
+ "major_version": 2,
+ "minor_version": 0,
+ "__X_HTTP_RESULT_INDEX": "2"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Switching Protocols",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 101,
+ "Connection": "Upgrade",
+ "Upgrade": "h2c",
+ "__X_HTTP_RESULT_INDEX": "3"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_upgrade_websocket.json b/test/test_result_json/http_upgrade_websocket.json
new file mode 100644
index 0000000..33e7336
--- /dev/null
+++ b/test/test_result_json/http_upgrade_websocket.json
@@ -0,0 +1,42 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "131.179.196.220.59631>131.179.196.46.9696",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "spurs.cs.ucla.edu:9696",
+ "Connection": "Upgrade",
+ "Pragma": "no-cache",
+ "Cache-Control": "no-cache",
+ "Upgrade": "websocket",
+ "Origin": "null",
+ "Sec-WebSocket-Version": "13",
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36",
+ "Accept-Encoding": "gzip, deflate, sdch",
+ "Accept-Language": "en-US,en;q=0.8,lv;q=0.6,ru;q=0.4",
+ "Cookie": "s_cc=true; s_sq=%5B%5BB%5D%5D; iwe_user_noticecount_urn%3amace%3aucla.edu%3appid%3aperson%3a1223EF7211FC4EC1965579D0B8D85FBA=2; __utma=125574670.1759122974.1407127284.1407127284.1415755402.2; __utmc=125574670; __utma=126236063.2139843507.1390525421.1433785187.1435706244.46; __utmc=126236063; __utmz=126236063.1427934389.33.5.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided); _ucla_sso=2015-07-02T11%3A34%3A30-07%3A00; _ga=GA1.2.1759122974.1407127284",
+ "Sec-WebSocket-Key": "sgD1adxQ3mk6BbBqab7owA==",
+ "Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits",
+ "__X_HTTP_URL": "spurs.cs.ucla.edu:9696/",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.1",
+ "res_status": "Switching Protocols",
+ "major_version": 1,
+ "minor_version": 1,
+ "status_code": 101,
+ "Connection": "upgrade",
+ "Sec-WebSocket-Accept": "FRh9fmH0UaoLdY5BSFO4hP2Pcjw=",
+ "Server": "WebSocket++/0.5.1",
+ "Upgrade": "websocket",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_url_test_with_host.json b/test/test_result_json/http_url_test_with_host.json
new file mode 100644
index 0000000..a7fe660
--- /dev/null
+++ b/test/test_result_json/http_url_test_with_host.json
@@ -0,0 +1,35 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.244.1.52412>192.168.244.128.8080",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/urltest/ttt",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "Host": "192.168.244.128:8080",
+ "Connection": "keep-alive",
+ "Upgrade-Insecure-Requests": "1",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
+ "Accept-Encoding": "gzip, deflate",
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
+ "__X_HTTP_URL": "192.168.244.128:8080/urltest/ttt",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.0",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 0,
+ "status_code": 200,
+ "Server": "BaseHTTP/0.6 Python/3.6.8",
+ "Date": "Thu, 14 Mar 2024 07:37:43 GMT",
+ "Content-type": "application/json",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/http_url_test_without_host.json b/test/test_result_json/http_url_test_without_host.json
new file mode 100644
index 0000000..9c91498
--- /dev/null
+++ b/test/test_result_json/http_url_test_without_host.json
@@ -0,0 +1,29 @@
+[
+ {
+ "__X_HTTP_TUPLE4": "192.168.244.128.44868>192.168.244.128.8080",
+ "__X_HTTP_RESULT_INDEX": "0"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "request",
+ "method": "GET",
+ "uri": "/urltest/ttt",
+ "req_version": "1.1",
+ "major_version": 1,
+ "minor_version": 1,
+ "User-Agent": "no h",
+ "__X_HTTP_URL": "192.168.244.128:8080/urltest/ttt",
+ "__X_HTTP_RESULT_INDEX": "1"
+ },
+ {
+ "__X_HTTP_TRANSACTION": "response",
+ "res_version": "1.0",
+ "res_status": "OK",
+ "major_version": 1,
+ "minor_version": 0,
+ "status_code": 200,
+ "Server": "BaseHTTP/0.6 Python/3.6.8",
+ "Date": "Thu, 14 Mar 2024 06:15:20 GMT",
+ "Content-type": "application/json",
+ "__X_HTTP_RESULT_INDEX": "2"
+ }
+] \ No newline at end of file
diff --git a/test/test_result_json/non_http.json b/test/test_result_json/non_http.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/test/test_result_json/non_http.json
@@ -0,0 +1 @@
+[] \ No newline at end of file
diff --git a/vendor/.gitkeep b/vendor/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vendor/.gitkeep
diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt
new file mode 100644
index 0000000..1f15068
--- /dev/null
+++ b/vendor/CMakeLists.txt
@@ -0,0 +1,53 @@
+include(ExternalProject)
+
+set(VENDOR_BUILD ${CMAKE_BINARY_DIR}/vendor/vbuild)
+
+# GoogleTest
+ExternalProject_Add(googletest PREFIX googletest
+ URL ${CMAKE_CURRENT_SOURCE_DIR}/googletest-release-1.8.0.tar.gz
+ URL_MD5 16877098823401d1bf2ed7891d7dce36
+ CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR> -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})
+
+ExternalProject_Get_Property(googletest INSTALL_DIR)
+file(MAKE_DIRECTORY ${INSTALL_DIR}/include)
+
+add_library(gtest STATIC IMPORTED GLOBAL)
+add_dependencies(gtest googletest)
+set_property(TARGET gtest PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libgtest.a)
+set_property(TARGET gtest PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include)
+set_property(TARGET gtest PROPERTY INTERFACE_LINK_LIBRARIES pthread)
+
+add_library(gmock STATIC IMPORTED GLOBAL)
+add_dependencies(gmock googletest)
+set_property(TARGET gmock PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libgmock.a)
+set_property(TARGET gmock PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include)
+
+#llhttp-9.1.2
+ExternalProject_Add(llhttp PREFIX llhttp
+ URL ${CMAKE_CURRENT_SOURCE_DIR}/llhttp-release-v9.1.3.tar.gz
+ CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${VENDOR_BUILD} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_C_FLAGS="-fPIC")
+
+file(MAKE_DIRECTORY ${VENDOR_BUILD}/include)
+
+add_library(llhttp-static STATIC IMPORTED GLOBAL)
+add_dependencies(llhttp-static llhttp)
+
+set_property(TARGET llhttp-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${VENDOR_BUILD}/include)
+set_property(TARGET llhttp-static PROPERTY IMPORTED_LOCATION ${VENDOR_BUILD}/lib64/libllhttp.a)
+
+#libcjson-1.7.17
+ExternalProject_Add(cjson PREFIX cjson
+URL ${CMAKE_CURRENT_SOURCE_DIR}/libcjson_v1.7.17.tar.gz
+URL_MD5 4b2ab12cf065c079004aa44495ade04a
+CONFIGURE_COMMAND ""
+BUILD_COMMAND make
+INSTALL_COMMAND make install
+BUILD_IN_SOURCE 1)
+
+ExternalProject_Get_Property(cjson INSTALL_DIR)
+file(MAKE_DIRECTORY ${VENDOR_BUILD}/include/cjson)
+
+add_library(cjson-static STATIC IMPORTED GLOBAL)
+add_dependencies(cjson-static cjson)
+set_property(TARGET cjson-static PROPERTY IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/vendor/cjson/src/cjson/libcjson.a)
+set_property(TARGET cjson-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${VENDOR_BUILD}/include/cjson)
diff --git a/vendor/googletest-release-1.8.0.tar.gz b/vendor/googletest-release-1.8.0.tar.gz
new file mode 100644
index 0000000..a40df33
--- /dev/null
+++ b/vendor/googletest-release-1.8.0.tar.gz
Binary files differ
diff --git a/vendor/libcjson_v1.7.17.tar.gz b/vendor/libcjson_v1.7.17.tar.gz
new file mode 100644
index 0000000..5d023fc
--- /dev/null
+++ b/vendor/libcjson_v1.7.17.tar.gz
Binary files differ
diff --git a/vendor/llhttp-release-v9.1.3.tar.gz b/vendor/llhttp-release-v9.1.3.tar.gz
new file mode 100644
index 0000000..c83dbd0
--- /dev/null
+++ b/vendor/llhttp-release-v9.1.3.tar.gz
Binary files differ