diff options
62 files changed, 7125 insertions, 0 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..d12c7c7 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,173 @@ +variables: + GIT_STRATEGY: "clone" + BUILD_IMAGE_CENTOS8: "git.mesalab.cn:7443/mesa_platform/build-env:rockylinux" + BUILD_PADDING_PREFIX: /tmp/padding_for_CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX_PREFIX_PREFIX_PREFIX_PREFIX_PREFIX/ + INSTALL_DEPENDENCY_PLATFORM: sapp-devel libasan systemd-devel libnsl glib2-devel libMESA_field_stat2 libfieldstat3 mrzcpd-corei7 hasp-tools libuuid-devel libbreakpad_mini + INSTALL_DEPENDENCY_FRAMEWORK: libMESA_handle_logger-devel libMESA_handle_logger framework_env + libMESA_prof_load-devel libMESA_prof_load libbreakpad_mini-devel libMESA_htable-devel libMESA_htable + stellar-on-sapp-devel stellar-on-sapp systemd-devel + libMESA_jump_layer libcjson-devel libcjson libfieldstat4-devel libfieldstat4 + INSTALL_PREFIX: "/opt/tsg/" + RPM_REPO_PREFIX: stellar + +stages: +- build +- test +- package + +.build_before_script: + 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 + - yum install -y elfutils-libelf-devel + +.build_by_travis_for_centos8: + stage: build + image: $BUILD_IMAGE_CENTOS8 + extends: .build_before_script + script: + - dnf --enablerepo=powertools install -y libmnl-devel + - dnf --enablerepo=powertools install -y libnfnetlink-devel + - ./ci/travis.sh + tags: + - share + +branch_build_debug_for_centos8: + stage: build + extends: .build_by_travis_for_centos8 + variables: + BUILD_TYPE: Debug + except: + - /^develop.*$/i + - /^master.*$/i + - tags + +branch_build_release_for_centos8: + stage: build + variables: + BUILD_TYPE: RelWithDebInfo + extends: .build_by_travis_for_centos8 + except: + - /^develop.*$/i + - /^master.*$/i + - tags + +develop_build_debug_for_centos8: + stage: build + extends: .build_by_travis_for_centos8 + variables: + BUILD_TYPE: Debug + PACKAGE: 1 + UPLOAD_RPM: 1 + ASAN_OPTION: ADDRESS + TESTING_VERSION_BUILD: 1 + PULP3_REPO_NAME: $RPM_REPO_PREFIX-testing-x86_64.el8 + PULP3_DIST_NAME: $RPM_REPO_PREFIX-testing-x86_64.el8 + artifacts: + name: "ftp_decoder-$CI_COMMIT_REF_NAME-debug" + paths: + - build/*.rpm + only: + - /^develop.*$/i + - /^master.*$/i + +develop_build_release_for_centos8: + stage: build + extends: .build_by_travis_for_centos8 + variables: + BUILD_TYPE: RelWithDebInfo + PACKAGE: 1 + UPLOAD_RPM: 1 + TESTING_VERSION_BUILD: 1 + PULP3_REPO_NAME: $RPM_REPO_PREFIX-testing-x86_64.el8 + PULP3_DIST_NAME: $RPM_REPO_PREFIX-testing-x86_64.el8 + artifacts: + name: "ftp_decoder-$CI_COMMIT_REF_NAME-release" + paths: + - build/*.rpm + only: + - /^develop.*$/i + - /^master.*$/i + +release_build_debug_for_centos8: + stage: package + variables: + BUILD_TYPE: Debug + PACKAGE: 1 + UPLOAD_RPM: 1 + ASAN_OPTION: ADDRESS + PULP3_REPO_NAME: $RPM_REPO_PREFIX-stable-x86_64.el8 + PULP3_DIST_NAME: $RPM_REPO_PREFIX-stable-x86_64.el8 + extends: .build_by_travis_for_centos8 + artifacts: + name: "ftp_decoder-$CI_COMMIT_REF_NAME-debug" + paths: + - build/*.rpm + only: + - tags + +release_build_release_for_centos8: + stage: package + variables: + BUILD_TYPE: RelWithDebInfo + PACKAGE: 1 + UPLOAD_RPM: 1 + UPLOAD_SYMBOL_FILES: 1 + SYMBOL_TARGET: ftp_decoder + PULP3_REPO_NAME: $RPM_REPO_PREFIX-stable-x86_64.el8 + PULP3_DIST_NAME: $RPM_REPO_PREFIX-stable-x86_64.el8 + extends: .build_by_travis_for_centos8 + artifacts: + name: "ftp_decoder-$CI_COMMIT_REF_NAME-release" + paths: + - build/*.rpm + only: + - tags + +############################################################################### +# test +############################################################################### + +.install_rpm_package: &install_rpm_package + - rpm -e sapp || true + - rpm -e sapp-devel || true + - rpm -e stellar-on-sapp || true + - rpm -e stellar-on-sapp-devel || true + - rpm -ivh /tmp/sapp.x86_64.rpm --prefix=${INSTALL_PREFIX}/sapp --force --nodeps + - rpm -ivh /tmp/sapp-devel.x86_64.rpm --prefix=${INSTALL_PREFIX}/sapp --force --nodeps + - rpm -ivh /tmp/stellar-on-sapp.x86_64.rpm --prefix=${INSTALL_PREFIX} --force --nodeps + - rpm -qa | grep sapp + - rpm -qa | grep stellar-on-sapp + - ls -l /opt/MESA/lib && echo "/opt/MESA/lib" >> /etc/ld.so.conf + + +history_version_test: + stage: test + extends: .build_by_travis_for_centos8 + script: + - yum makecache + - ./ci/travis.sh + - cp -f ./vendor/stellar-on-sapp*.rpm /tmp/stellar-on-sapp.x86_64.rpm + - cp -f ./vendor/sapp-devel*.rpm /tmp/sapp-devel.x86_64.rpm + - cp -f ./vendor/sapp-4*.rpm /tmp/sapp.x86_64.rpm + - *install_rpm_package + - cd build + - ctest3 --verbose + +latest_version_test: + stage: test + extends: .build_by_travis_for_centos8 + script: + - yum makecache + - ./ci/travis.sh + - rm -f stellar-on-sapp*.rpm sapp*.rpm + - yumdownloader stellar-on-sapp sapp-devel sapp + - cp -f stellar-on-sapp*.rpm /tmp/stellar-on-sapp.x86_64.rpm + - cp -f sapp-devel*.rpm /tmp/sapp-devel.x86_64.rpm + - cp -f sapp-4*.rpm /tmp/sapp.x86_64.rpm + - *install_rpm_package + - cd build + - ctest3 --verbose
\ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5cbbb11 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required (VERSION 2.8...3.10) + +set(lib_name ftp_decoder) + +project (${lib_name}) + +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +include(Version) + +set(CMAKE_MACOSX_RPATH 0) +set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -Wall) + +#for ASAN +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 MATCHES "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") +elseif(ASAN_OPTION MATCHES "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") +endif() +# end of for ASAN + +set(CMAKE_INSTALL_PREFIX /opt/tsg) + +include_directories(include) +include_directories(/opt/MESA/include/MESA/) +include_directories(/opt/tsg/framework/include/) + +add_subdirectory(vendor) +add_subdirectory(src) + +enable_testing() +add_subdirectory(test) + +set(CPACK_RPM_USER_FILELIST "%config(noreplace) ${CMAKE_INSTALL_PREFIX}/sapp/conf/ftp_decoder/ftp.conf") + +install(FILES bin/ftp.conf DESTINATION ${CMAKE_INSTALL_PREFIX}/sapp/conf/ftp_decoder COMPONENT PROFILE) +install(FILES include/ftp_decoder.h DESTINATION ${CMAKE_INSTALL_PREFIX}/framework/include/ftp_decoder COMPONENT HEADER) + +include(Package) diff --git a/autorevision.sh b/autorevision.sh new file mode 100644 index 0000000..3baa179 --- /dev/null +++ b/autorevision.sh @@ -0,0 +1,1268 @@ +#!/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/bin/ftp.conf b/bin/ftp.conf new file mode 100644 index 0000000..7d8c6bf --- /dev/null +++ b/bin/ftp.conf @@ -0,0 +1,3 @@ +[FTP] +log_level=10 +log_path=./log/ftp_decoder.log diff --git a/bin/ftp_decoder.inf b/bin/ftp_decoder.inf new file mode 100644 index 0000000..3e3005d --- /dev/null +++ b/bin/ftp_decoder.inf @@ -0,0 +1,9 @@ +[[plugin]] +path = "./stellar_plugin/ftp_decoder/ftp_decoder.so" +init = "FTP_ONLOAD" +exit = "FTP_UNLOAD" + +[[plugin]] +path = "./stellar_plugin/ftp_decoder/ftp_decoder_test_plug.so" +init = "FTP_TEST_PLUG_INIT" +exit = "FTP_TEST_PLUG_DESTROY" diff --git a/ci/get-nprocessors.sh b/ci/get-nprocessors.sh new file mode 100644 index 0000000..43635e7 --- /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/perpare_pulp3_netrc.sh b/ci/perpare_pulp3_netrc.sh new file mode 100644 index 0000000..8414bbb --- /dev/null +++ b/ci/perpare_pulp3_netrc.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +set -evx +echo "machine ${PULP3_SERVER_URL}\nlogin ${PULP3_SERVER_LOGIN}\npassword ${PULP3_SERVER_PASSWORD}\n" > ~/.netrc diff --git a/ci/travis.sh b/ci/travis.sh new file mode 100644 index 0000000..7a4cdc1 --- /dev/null +++ b/ci/travis.sh @@ -0,0 +1,69 @@ +#!/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}" + +# Install dependency from YUM +if [ -n "${INSTALL_DEPENDENCY_FRAMEWORK}" ]; then + yum install -y $INSTALL_DEPENDENCY_FRAMEWORK + source /etc/profile.d/framework.sh +fi + +if [ -n "${INSTALL_DEPENDENCY_PLATFORM}" ]; then + yum install -y $INSTALL_DEPENDENCY_PLATFORM +fi + +if [ $ASAN_OPTION ] && [ -f "/opt/rh/devtoolset-7/enable" ] ;then + source /opt/rh/devtoolset-7/enable +fi + +mkdir build || true +cd build + +cmake3 -DCMAKE_CXX_FLAGS=$CXX_FLAGS \ + -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ + -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \ + -DASAN_OPTION=$ASAN_OPTION \ + -DVERSION_DAILY_BUILD=$TESTING_VERSION_BUILD \ + .. + +make + +if [ -n "${PACKAGE}" ]; then + make package +fi + +if [ -n "${UPLOAD_RPM}" ]; then + cp ~/rpm_upload_tools.py ./ + python3 rpm_upload_tools.py ${PULP3_REPO_NAME} ${PULP3_DIST_NAME} *.rpm +fi + diff --git a/cmake/Package.cmake b/cmake/Package.cmake new file mode 100644 index 0000000..d7c7dd3 --- /dev/null +++ b/cmake/Package.cmake @@ -0,0 +1,56 @@ +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(MY_RPM_NAME_PREFIX "${lib_name}-debug") +else() + set(MY_RPM_NAME_PREFIX "${lib_name}") +endif() + +message(STATUS "Package: ${MY_RPM_NAME_PREFIX}") + +set(CPACK_PACKAGE_VECDOR "MESA") +set(CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}.${VERSION_BUILD}") +set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) +set(CPACK_PACKAGE_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_BUILD}") + +execute_process(COMMAND sh changelog.sh ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/cmake) +SET(CPACK_RPM_CHANGELOG_FILE ${CMAKE_BINARY_DIR}/changelog.txt) + +# 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_VENDOR "MESA") +set(CPACK_RPM_PACKAGE_AUTOREQPROV "yes") +set(CPACK_RPM_PACKAGE_RELEASE_DIST "on") +set(CPACK_RPM_DEBUGINFO_PACKAGE "on") + +set(CPACK_RPM_COMPONENT_INSTALL ON) +set(CPACK_COMPONENTS_IGNORE_GROUPS 1) +set(CPACK_COMPONENTS_GROUPING ONE_PER_GROUP) +set(CPACK_COMPONENT_HEADER_DISPLAY_NAME "develop") + +set(CPACK_COMPONENT_LIBRARIES_REQUIRED TRUE) +set(CPACK_RPM_LIBRARIES_PACKAGE_NAME ${MY_RPM_NAME_PREFIX}) +set(CPACK_COMPONENT_LIBRARIES_GROUP "LIBRARIES") +set(CPACK_COMPONENT_PROFILE_GROUP "LIBRARIES") + +set(CPACK_COMPONENT_HEADER_REQUIRED TRUE) +set(CPACK_RPM_HEADER_PACKAGE_NAME "${MY_RPM_NAME_PREFIX}-devel") +set(CPACK_COMPONENT_HEADER_GROUP "HEADER") + +set(CPACK_RPM_HEADER_PACKAGE_REQUIRES_PRE ${CPACK_RPM_LIBRARIES_PACKAGE_NAME}) +set(CPACK_RPM_HEADER_PACKAGE_CONFLICTS ${CPACK_RPM_HEADER_PACKAGE_NAME}) + +set(CPACK_COMPONENTS_ALL LIBRARIES HEADER PROFILE) + +SET(CPACK_RPM_LIBRARIES_PRE_INSTALL_SCRIPT_FILE "${PROJECT_SOURCE_DIR}/cmake/preInstall.sh") +SET(CPACK_RPM_LIBRARIES_PRE_UNINSTALL_SCRIPT_FILE "${PROJECT_SOURCE_DIR}/cmake/preUninstall.sh") + +set(CPACK_BUILD_SOURCE_DIRS "${CMAKE_SOURCE_DIR}") + +# Must uninstall the debug package before install release package +set(CPACK_RPM_PACKAGE_CONFLICTS ${MY_RPM_NAME_PREFIX}) + +# set(CPACK_STRIP_FILES TRUE) +include(CPack) diff --git a/cmake/Version.cmake b/cmake/Version.cmake new file mode 100644 index 0000000..a47944c --- /dev/null +++ b/cmake/Version.cmake @@ -0,0 +1,54 @@ + +# 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} ERROR_QUIET) +include(${__VERSION_CONFIG}) + +# extract major, minor, patch version from git tag +string(REGEX REPLACE "^v([0-9]+)\\..*" "\\1" VERSION_MAJOR "${VCS_TAG}") +string(REGEX REPLACE "^v[0-9]+\\.([0-9]+).*" "\\1" VERSION_MINOR "${VCS_TAG}") +string(REGEX REPLACE "^v[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" VERSION_PATCH "${VCS_TAG}") +string(REGEX REPLACE "[T\\:\\+\\-]" "" VERSION_DATE "${VCS_DATE}") + +if(VERSION_DAILY_BUILD) + set(VERSION_PATCH ${VERSION_PATCH}.${VERSION_DATE}) +endif() + +if(NOT VERSION_MAJOR) + set(VERSION_MAJOR 1) +endif() + +if(NOT VERSION_MINOR) + set(VERSION_MINOR 0) +endif() + +if(NOT VERSION_PATCH) + set(VERSION_PATCH 0) +endif() + +set(VERSION "${VERSION_MAJOR}_${VERSION_MINOR}_${VERSION_PATCH}") +set(VERSION_BUILD "${VCS_SHORT_HASH}") + +# print information +message(STATUS "Version: ${VERSION}-${VERSION_BUILD}") + +option(DEFINE_GIT_VERSION "Set DEFINE_GIT_VERSION to TRUE or FALSE" TRUE) + +if(DEFINE_GIT_VERSION) + set(GIT_VERSION + "${VERSION}-${CMAKE_BUILD_TYPE}-${VERSION_BUILD}-${VCS_BRANCH}-${VCS_TAG}-${VCS_DATE}") + string(REGEX REPLACE "[-:+/\\.]" "_" GIT_VERSION ${GIT_VERSION}) + + add_definitions(-DGIT_VERSION=${GIT_VERSION}) +endif() diff --git a/cmake/changelog.sh b/cmake/changelog.sh new file mode 100644 index 0000000..087129d --- /dev/null +++ b/cmake/changelog.sh @@ -0,0 +1,4 @@ +#!/bin/sh +work_path=$1 +branch=`git status | grep 'On branch' | awk '{print $NF}'` +git log --branches=$branch --no-merges --date=local --show-signature --pretty="* %ad %an %ae %nhash: %H%ncommit:%n%B" | awk -F"-" '{print "- "$0}' | sed 's/- \*/\*/g' | sed 's/- $//g' | sed 's/-/ -/g' | sed 's/[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}//g' > $work_path/changelog.txt diff --git a/cmake/preInstall.sh b/cmake/preInstall.sh new file mode 100644 index 0000000..0668303 --- /dev/null +++ b/cmake/preInstall.sh @@ -0,0 +1,14 @@ +#!/bin/sh +DST=${RPM_INSTALL_PREFIX}/sapp/ + +mkdir -p ${DST}/stellar_plugin +touch ${DST}/stellar_plugin/spec.toml + +if ! grep -q '^\./plug/stellar_plugin/ftp_decoder/ftp_decoder.so' "${DST}/stellar_plugin/spec.toml"; then + echo -e "\n" >> "${DST}/stellar_plugin/spec.toml" + echo -e "[[plugin]]" >> "${DST}/stellar_plugin/spec.toml" + echo -e "path = \"./stellar_plugin/ftp_decoder/ftp_decoder.so\"" >> "${DST}/stellar_plugin/spec.toml" + echo -e "init = \"FTP_ONLOAD\"" >> "${DST}/stellar_plugin/spec.toml" + echo -e "exit = \"FTP_UNLOAD\"" >> "${DST}/stellar_plugin/spec.toml" + echo -e "\n" >> "${DST}/stellar_plugin/spec.toml" +fi diff --git a/cmake/preUninstall.sh b/cmake/preUninstall.sh new file mode 100644 index 0000000..3e54faf --- /dev/null +++ b/cmake/preUninstall.sh @@ -0,0 +1,6 @@ +if [ $1 == 0 ]; then + DST=${RPM_INSTALL_PREFIX}/sapp/stellar_plugin + sed -i -n '$!N;/ftp_decoder.so/!P;D' ${DST}/spec.toml + sed -i '/FTP_ONLOAD/d' ${DST}/spec.toml + sed -i '/FTP_UNLOAD/d' ${DST}/spec.toml +fi diff --git a/include/ftp_decoder.h b/include/ftp_decoder.h new file mode 100644 index 0000000..206ece9 --- /dev/null +++ b/include/ftp_decoder.h @@ -0,0 +1,38 @@ +#pragma once +#ifdef __cplusplus +extern "C" +{ +#endif +#include <bits/types/struct_iovec.h> + +#define FTP_DECODER_CTRL_TOPIC "FTP_DECODER_CTRL_MESSAGE" +#define FTP_DECODER_DATA_TOPIC "FTP_DECODER_DATA_MESSAGE" + +struct ftp_message; + +enum ftp_msg_type{ + FTP_BANNER = 0, + FTP_ACCOUNT, + FTP_PASSWORD, + FTP_URI, + FTP_TRANS_MODE, // "PASV", "PORT" + FTP_TRANS_DIR, // "RETR", "STOR" + FTP_INVENTORY, // by command "LIST" + FTP_FILE_CONTENT, // by command "RETR" and "STOR" + + FTP_MSG_MAX, +}; + +/** + * 1: success + * 0: failed or no available data +*/ +int ftp_message_iterate(const struct ftp_message *msg, enum ftp_msg_type *type, struct iovec *value); + +void ftp_message_reset_iterator(const struct ftp_message *msg); + +const char *ftp_message_type_to_string(enum ftp_msg_type fmsg_type); + +#ifdef __cplusplus +} +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..075e7fd --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required (VERSION 2.8...3.10) + +file(GLOB SRC "*.cpp") +set(DEPEND_DYN_LIB MESA_handle_logger MESA_prof_load MESA_htable fieldstat4) + +include_directories(/opt/tsg/framework/include/) + +# Shared Library Output +add_library(${lib_name} SHARED ${SRC}) +set_target_properties(${lib_name} PROPERTIES LINK_FLAGS "-Wl,--version-script=${PROJECT_SOURCE_DIR}/src/version.map") +set_target_properties(${lib_name} PROPERTIES PREFIX "") +target_link_libraries(${lib_name} ${DEPEND_DYN_LIB} ) +set_target_properties(${lib_name} PROPERTIES OUTPUT_NAME ${lib_name}) + +install(TARGETS ${lib_name} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/sapp/stellar_plugin/ftp_decoder COMPONENT LIBRARIES) diff --git a/src/ftp_decoder_entry.cpp b/src/ftp_decoder_entry.cpp new file mode 100644 index 0000000..3dc0df0 --- /dev/null +++ b/src/ftp_decoder_entry.cpp @@ -0,0 +1,227 @@ +#include "ftp_decoder_inner.h" +#include "ftp_decoder.h" +#include "ftp_decoder_util.h" +#include <stellar/session.h> +#include <sys/types.h> +#include <MESA/MESA_prof_load.h> +#include <MESA/MESA_handle_logger.h> + +void *__ftp_decoder_logger_handle; + +static void ftp_msg_free_cb(struct session *sess, void *msg, void *msg_free_arg) +{ + free(msg); +} + +static void *ftp_context_ctx_new_cb(struct session *sess, void *plugin_env) +{ + struct ftp_decoder_env *fenv = (struct ftp_decoder_env *)plugin_env; + size_t payload_len; + const char *payload = session_get0_current_payload(sess, &payload_len); + int curdir = ftp_session_get_pkt_dir(sess); + int thread_idx = session_get_current_thread_id(sess); + int is_ctrl_link = ftp_ctrl_identify(sess, payload, payload_len, curdir); + if (is_ctrl_link) + { + struct ftp_context *fctx = (struct ftp_context *)calloc(1, sizeof(struct ftp_context)); + fctx->link_type = FTP_LINK_CTRL; + ftp_decoder_stat_incrby(thread_idx, fenv, FTPD_STAT_CTRL_LINK_OPEN, 1); + ftp_runtime_log(RLOG_LV_DEBUG, "ftp ctrl link new: %s", session_get0_readable_addr(sess)); + return fctx; + } + ftp_runtime_log(RLOG_LV_DEBUG, "ftp_data_identify: is not ctrl link: '%s' ", session_get0_readable_addr(sess)); + + struct ftp_context *data_ctx = ftp_data_identify(sess, fenv); + if (data_ctx) + { + data_ctx->link_type = FTP_LINK_DATA; + ftp_decoder_stat_incrby(thread_idx, fenv, FTPD_STAT_DATA_LINK_OPEN, 1); + ftp_runtime_log(RLOG_LV_DEBUG, "ftp data link new: '%s' ", session_get0_readable_addr(sess)); + return data_ctx; + } + ftp_runtime_log(RLOG_LV_DEBUG, "ftp_data_identify: is not data link: '%s' ", session_get0_readable_addr(sess)); + stellar_session_plugin_dettach_current_session(sess); + return NULL; +} + +void ftp_context_ctx_free_cb(struct session *sess, void *session_ctx, void *plugin_env) +{ + struct ftp_context *fctx = (struct ftp_context *)session_ctx; + if (NULL == fctx) + { + return; + } + int thread_idx = session_get_current_thread_id(sess); + if (fctx->link_type == FTP_LINK_CTRL) + { + ftp_decoder_stat_incrby(thread_idx, (struct ftp_decoder_env *)plugin_env, FTPD_STAT_CTRL_LINK_CLOSE, 1); + } + else + { + ftp_decoder_stat_incrby(thread_idx, (struct ftp_decoder_env *)plugin_env, FTPD_STAT_DATA_LINK_CLOSE, 1); + } + ftp_runtime_log(RLOG_LV_DEBUG, "ftp_context_ctx_free_cb: %s", session_get0_readable_addr(sess)); + ftp_parse_result_free(&fctx->parse_result); + free(fctx); +} + +static void ftpd_logger_init(struct ftp_decoder_env *fenv) +{ + int log_level; + char log_path[256]; + MESA_load_profile_int_def(FTP_DECODER_CFG_FILE, "FTP", "log_level", &log_level, 30); + MESA_load_profile_string_def(FTP_DECODER_CFG_FILE, "FTP", "log_path", log_path, sizeof(log_path), "./log/ftp_decoder.log"); + MESA_handle_runtime_log_creation("./etc/sapp_log.conf"); + fenv->logger_handle = MESA_create_runtime_log_handle(log_path, log_level); + __ftp_decoder_logger_handle = fenv->logger_handle; + return; +} + +void ftp_message_reset_iterator(const struct ftp_message *msg) +{ + msg->fctx->parse_result.cursor = 0; +} + +static void ftp_ctrl_entry(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + size_t payload_len; + const char *payload = session_get0_current_payload(sess, &payload_len); + int curdir = ftp_session_get_pkt_dir(sess); + if (NULL == payload || payload_len == 0) + { + return; + } + int thread_idx = session_get_current_thread_id(sess); + ftp_cmd_readline(&fctx->cmd_result, payload, payload_len); + ftp_cmd_process(sess, fctx, fenv, curdir); + if (curdir == PACKET_DIRECTION_C2S) + { + ftp_decoder_stat_incrby(thread_idx, fenv, FTPD_STAT_CTRL_LINK_BYTES_C2S, (long long)payload_len); + } + else + { + ftp_decoder_stat_incrby(thread_idx, fenv, FTPD_STAT_CTRL_LINK_BYTES_S2C, (long long)payload_len); + } +} + +static void ftp_data_entry(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + enum session_state sstate = session_get_current_state(sess); + if (SESSION_STATE_OPENING == sstate) + { + memset(fctx->parse_result.push_result_flags, 0, sizeof(fctx->parse_result.push_result_flags)); + ftp_runtime_log(RLOG_LV_DEBUG, "ftp data link opening: %s", session_get0_readable_addr(sess)); + } + + size_t payload_len; + const char *payload = session_get0_current_payload(sess, &payload_len); + if (NULL == payload || payload_len == 0) + { + return; + } + enum ftp_msg_type dlink_presentation = fctx->data_link_presentation; + if (FTP_INVENTORY != dlink_presentation && FTP_FILE_CONTENT != dlink_presentation) + { + dlink_presentation = FTP_FILE_CONTENT; // unknown data link type, default as FTP_FILE_CONTENT + } + fctx->parse_result.result_array[dlink_presentation].iov_base = (void *)payload; + fctx->parse_result.result_array[dlink_presentation].iov_len = payload_len; + fctx->parse_result.push_result_flags[dlink_presentation] = 0; + + ftp_decoder_push_msg(sess, fctx, fenv); + int thread_idx = session_get_current_thread_id(sess); + int curdir = ftp_session_get_pkt_dir(sess); + if (curdir == PACKET_DIRECTION_C2S) + { + ftp_decoder_stat_incrby(thread_idx, fenv, FTPD_STAT_DATA_LINK_BYTES_C2S, (long long)payload_len); + } + else + { + ftp_decoder_stat_incrby(thread_idx, fenv, FTPD_STAT_DATA_LINK_BYTES_S2C, (long long)payload_len); + } +} + +static void ftp_decoder_entry(struct session *sess, int topic_id, const void *msg, void *per_session_ctx, void *plugin_env) +{ + struct ftp_context *fctx = (struct ftp_context *)per_session_ctx; + if (FTP_LINK_CTRL == fctx->link_type) + { + ftp_ctrl_entry(sess, fctx, (struct ftp_decoder_env *)plugin_env); + } + else + { + ftp_data_entry(sess, fctx, (struct ftp_decoder_env *)plugin_env); + } +} + +int ftp_message_iterate(const struct ftp_message *msg, enum ftp_msg_type *type, struct iovec *value) +{ + if (msg->fctx->parse_result.cursor >= FTP_MSG_MAX) + { + return 0; + } + int index = msg->fctx->parse_result.cursor; + for (int i = index; i < (int)FTP_MSG_MAX; i++) + { + if (msg->fctx->parse_result.push_result_flags[i] == 1) + { + continue; + } + if (msg->fctx->parse_result.result_array[i].iov_base == NULL || msg->fctx->parse_result.result_array[i].iov_len == 0) + { + continue; + } + *type = (enum ftp_msg_type)i; + value->iov_base = msg->fctx->parse_result.result_array[i].iov_base; + value->iov_len = msg->fctx->parse_result.result_array[i].iov_len; + msg->fctx->parse_result.push_result_flags[i] = 1; + msg->fctx->parse_result.cursor = index + 1; + return 1; + } + return 0; +} + +int ftp_decoder_push_msg(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + struct ftp_message *fmsg = (struct ftp_message *)calloc(1, sizeof(struct ftp_message)); + fmsg->fctx = fctx; + fctx->parse_result.cursor = 0; + if (FTP_LINK_CTRL == fctx->link_type) + { + session_mq_publish_message(sess, fenv->ftp_pub_ctrl_topic_id, fmsg); + } + else + { + session_mq_publish_message(sess, fenv->ftp_pub_data_topic_id, fmsg); + } + return 0; +} + +extern "C" void *FTP_ONLOAD(struct stellar *st) +{ + struct ftp_decoder_env *fenv = (struct ftp_decoder_env *)calloc(1, sizeof(struct ftp_decoder_env)); + + fenv->thread_count = stellar_get_worker_thread_num(st); + assert(fenv->thread_count >= 1); + fenv->plugin_id = stellar_session_plugin_register(st, ftp_context_ctx_new_cb, ftp_context_ctx_free_cb, fenv); + fenv->tcp_sub_topic_id = stellar_session_mq_get_topic_id(st, TOPIC_TCP_STREAM); + stellar_session_mq_subscribe(st, fenv->tcp_sub_topic_id, ftp_decoder_entry, fenv->plugin_id); + fenv->ftp_pub_ctrl_topic_id = stellar_session_mq_create_topic(st, FTP_DECODER_CTRL_TOPIC, ftp_msg_free_cb, fenv); + fenv->ftp_pub_data_topic_id = stellar_session_mq_create_topic(st, FTP_DECODER_DATA_TOPIC, ftp_msg_free_cb, fenv); + ftp_hash_table_create(fenv); + ftp_decoder_stat_init(fenv); + ftpd_logger_init(fenv); + ftp_runtime_log(RLOG_LV_DEBUG, "FTP_ONLOAD success."); + return fenv; +} + +extern "C" void FTP_UNLOAD(void *plugin_env) +{ + struct ftp_decoder_env *fenv = (struct ftp_decoder_env *)plugin_env; + ftp_runtime_log(RLOG_LV_DEBUG, "FTP_UNLOAD ..."); + ftp_hash_table_destroy(fenv); + ftp_decoder_stat_free(fenv); + __ftp_decoder_logger_handle = NULL; + MESA_destroy_runtime_log_handle(fenv->logger_handle); + free(fenv); +}
\ No newline at end of file diff --git a/src/ftp_decoder_hash.cpp b/src/ftp_decoder_hash.cpp new file mode 100644 index 0000000..752505c --- /dev/null +++ b/src/ftp_decoder_hash.cpp @@ -0,0 +1,169 @@ +#include "ftp_decoder_inner.h" +#include "ftp_decoder_hash.h" +#include "ftp_decoder_util.h" +#include <MESA_htable.h> +#include <cstdint> +#include <arpa/inet.h> + +static thread_local char __ftp_hash_string_buf[1024]; + +void ftp_declare_datalink_free(void *data) +{ + struct ftp_context *fctx = (struct ftp_context *)data; + ftp_parse_result_free(&fctx->parse_result); + free(fctx); +} + +static void ftp_declare_datalink_free_only_key(void *data) +{ + return; // do nothing +} + +int ftp_hash_add(void *handle, const void *key, u_int32_t key_len, void *value) +{ + if (MESA_htable_add(handle, (const uint8_t *)key, key_len, value) < 0) + { + ftp_declare_datalink_free(value); + return -1; + } + return 0; +} + +void ftp_hash_del(void *handle, const struct ftp_link_key *hkey) +{ + const void *key; + u_int32_t key_len; + if (SESSION_ADDR_TYPE_IPV4_TCP == hkey->addr_type || SESSION_ADDR_TYPE_IPV4_UDP == hkey->addr_type) + { + key = &hkey->tuplev4; + key_len = sizeof(struct session_addr_ipv4); + } + else + { + key = &hkey->tuplev6; + key_len = sizeof(struct session_addr_ipv6); + } + MESA_htable_del(handle, (const uint8_t *)key, key_len, ftp_declare_datalink_free_only_key); +} + +void ftp_make_hkey_v4(struct session_addr_ipv4 *keyv4, uint32_t sip_net, uint32_t dip_net, uint16_t dip_port_net) +{ + memset(keyv4, 0, sizeof(struct session_addr_ipv4)); + keyv4->saddr = sip_net; + keyv4->daddr = dip_net; + keyv4->sport = 0; + keyv4->dport = dip_port_net; +} + +void ftp_make_hkey_v6(struct session_addr_ipv6 *keyv6, const struct in6_addr *sip, const struct in6_addr *dip, uint16_t dip_port_net) +{ + memset(keyv6, 0, sizeof(struct session_addr_ipv6)); + memcpy(keyv6->saddr, sip, IPV6_ADDR_LEN); + memcpy(keyv6->daddr, dip, IPV6_ADDR_LEN); + keyv6->sport = 0; + keyv6->dport = dip_port_net; +} + +int ftp_build_hashkey_from_session(struct session *sess, struct ftp_link_key *hkey) +{ + enum session_addr_type saddr_type; + struct session_addr *saddr = session_get0_addr(sess, &saddr_type); + hkey->addr_type = saddr_type; + if (SESSION_ADDR_TYPE_IPV4_TCP == saddr_type) + { + ftp_make_hkey_v4(&hkey->tuplev4, saddr->ipv4.saddr, saddr->ipv4.daddr, saddr->ipv4.dport); + } + else if (SESSION_ADDR_TYPE_IPV6_TCP == saddr_type) + { + ftp_make_hkey_v6(&hkey->tuplev6, (struct in6_addr *)saddr->ipv6.saddr, (struct in6_addr *)saddr->ipv6.daddr, saddr->ipv6.dport); + } + else + { + return -1; + } + return 0; +} + +void *ftp_hash_search(void *handle, const struct ftp_link_key *hkey) +{ + const void *key; + u_int32_t key_len; + if (SESSION_ADDR_TYPE_IPV4_TCP == hkey->addr_type || SESSION_ADDR_TYPE_IPV4_UDP == hkey->addr_type) + { + key = &hkey->tuplev4; + key_len = sizeof(struct session_addr_ipv4); + } + else + { + key = &hkey->tuplev6; + key_len = sizeof(struct session_addr_ipv6); + } + return MESA_htable_search(handle, (const uint8_t *)key, key_len); +} + +const char *ftp_hash_key_to_str(const struct ftp_link_key *hkey) +{ + char sip_str[INET6_ADDRSTRLEN], dip_str[INET6_ADDRSTRLEN]; + unsigned short sport_host, dport_host; + if (SESSION_ADDR_TYPE_IPV4_TCP == hkey->addr_type || SESSION_ADDR_TYPE_IPV4_UDP == hkey->addr_type) + { + inet_ntop(AF_INET, &hkey->tuplev4.saddr, sip_str, INET_ADDRSTRLEN); + inet_ntop(AF_INET, &hkey->tuplev4.daddr, dip_str, INET_ADDRSTRLEN); + sport_host = ntohs(hkey->tuplev4.sport); + dport_host = ntohs(hkey->tuplev4.dport); + } + else + { + inet_ntop(AF_INET, hkey->tuplev6.saddr, sip_str, INET6_ADDRSTRLEN); + inet_ntop(AF_INET, hkey->tuplev6.daddr, dip_str, INET6_ADDRSTRLEN); + sport_host = ntohs(hkey->tuplev6.sport); + dport_host = ntohs(hkey->tuplev6.dport); + } + snprintf(__ftp_hash_string_buf, sizeof(__ftp_hash_string_buf), "%s:%u -> %s:%u", sip_str, sport_host, dip_str, dport_host); + return __ftp_hash_string_buf; +} + +int ftp_hash_table_create(struct ftp_decoder_env *fenv) +{ + int opt, opt_len; + fenv->data_link_table = (void **)calloc(fenv->thread_count, sizeof(void *)); + for (int i = 0; i < fenv->thread_count; i++) + { + fenv->data_link_table[i] = MESA_htable_born(); + assert(fenv->data_link_table[i]); + + opt = 4096; + opt_len = sizeof(int); + MESA_htable_set_opt(fenv->data_link_table[i], MHO_HASH_SLOT_SIZE, &opt, opt_len); + + opt = 0; + opt_len = sizeof(int); + MESA_htable_set_opt(fenv->data_link_table[i], MHO_THREAD_SAFE, &opt, opt_len); + + opt = 0; + opt_len = sizeof(int); + MESA_htable_set_opt(fenv->data_link_table[i], MHO_SCREEN_PRINT_CTRL, &opt, opt_len); + + opt = 10; + opt_len = sizeof(int); + MESA_htable_set_opt(fenv->data_link_table[i], MHO_EXPIRE_TIME, &opt, opt_len); + MESA_htable_set_opt(fenv->data_link_table[i], MHO_CBFUN_DATA_FREE, (void *)&ftp_declare_datalink_free, sizeof(void *)); + MESA_htable_mature(fenv->data_link_table[i]); + } + return 0; +} + +void ftp_hash_table_destroy(struct ftp_decoder_env *fenv) +{ + unsigned int remain_items; + for (int i = 0; i < fenv->thread_count; i++) + { + remain_items = MESA_htable_get_elem_num(fenv->data_link_table[0]); + if (remain_items > 0) + { + ftp_runtime_log(RLOG_LV_DEBUG, "ftp_hash_table_destroy(): thread:%d declare datalink table remain items:%u\n", i, remain_items); + } + MESA_htable_destroy(fenv->data_link_table[i], ftp_declare_datalink_free); + } + free(fenv->data_link_table); +}
\ No newline at end of file diff --git a/src/ftp_decoder_hash.h b/src/ftp_decoder_hash.h new file mode 100644 index 0000000..21a702f --- /dev/null +++ b/src/ftp_decoder_hash.h @@ -0,0 +1,38 @@ +#pragma once +#ifdef __cplusplus +extern "C" +{ +#endif +#include <stellar/session.h> +#include <stellar/session_exdata.h> +#include <stellar/session_mq.h> +#include <stellar/stellar.h> +#ifdef __cplusplus +} +#endif +#include <stddef.h> +#include <MESA/MESA_htable.h> + +/* + for ftp passive mode, source port is random, so we can't use tuple4 to identify data link. + so always set source port to 0! +*/ +struct ftp_link_key +{ + enum session_addr_type addr_type; + int __pad__; + union + { + struct session_addr_ipv4 tuplev4; + struct session_addr_ipv6 tuplev6; + }; +} __attribute__((packed)); + +int ftp_hash_add(void *handle, const void *key, u_int32_t key_len, void *value); +void *ftp_hash_search(void *handle, const struct ftp_link_key *hkey); +void ftp_hash_del(void *handle, const struct ftp_link_key *hkey); +void ftp_declare_datalink_free(void *data); +void ftp_make_hkey_v4(struct session_addr_ipv4 *keyv4, uint32_t sip_net, uint32_t dip_net, uint16_t dip_port_net); +void ftp_make_hkey_v6(struct session_addr_ipv6 *keyv6, const struct in6_addr *sip, const struct in6_addr *dip, uint16_t dip_port_net); +const char *ftp_hash_key_to_str(const struct ftp_link_key *hkey); +int ftp_build_hashkey_from_session(struct session *sess, struct ftp_link_key *hkey);
\ No newline at end of file diff --git a/src/ftp_decoder_inner.h b/src/ftp_decoder_inner.h new file mode 100644 index 0000000..9f632a1 --- /dev/null +++ b/src/ftp_decoder_inner.h @@ -0,0 +1,125 @@ +#pragma once +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include "ftp_decoder.h" +#include <MESA/MESA_handle_logger.h> +#ifdef __cplusplus +extern "C" +{ +#endif +#include <stellar/session.h> +#include <stellar/session_exdata.h> +#include <stellar/session_mq.h> +#include <stellar/stellar.h> +#ifdef __cplusplus +} +#endif +#include "ftp_decoder_stat.h" +#include "ftp_decoder_hash.h" + +#define FTPD_IDENTIRY_MIN_LEN 4 +#define FTPD_IDENTIRY_MAX_LEN 32 +#define FTPD_CMD_MAX_LENGTH 128 +#define FTP_DECODER_CFG_FILE "./conf/ftp_decoder/ftp.conf" +#define FTP_DECODER_FIELDSTAT_NAME "ftp_decoder_statistics" +#define FTP_DECODER_FIELDSTAT_OUTPUT_FILE "./metrics/ftp_decoder_fs4.json" +#define FTP_DECODER_FIELDSTAT_OUTPUT_INTERVAL 3 + +extern void *__ftp_decoder_logger_handle; +#define ftp_runtime_log(log_level, format, ...) \ + do \ + { \ + if (MESA_handle_runtime_log_level_enabled(__ftp_decoder_logger_handle, log_level)) \ + { \ + MESA_handle_runtime_log(__ftp_decoder_logger_handle, log_level, "ftp_decoder", format, ##__VA_ARGS__); \ + } \ + } while (0) + +#define IOVEC_PRINT(iov) (int)(iov).iov_len, (char *)(iov).iov_base +#define IOVEC_PRINT_PTR(iov_p) (int)(iov_p->iov_len), (char *)(iov_p->iov_base) +#ifndef fstring +typedef struct iovec fstring; +#endif +enum ftp_link_type +{ + FTP_LINK_CTRL, + FTP_LINK_DATA, +}; + +enum ftp_data_link_type +{ + FTP_DATA_LINK_FILE, + FTP_DATA_LINK_INVENTORY, +}; + +struct ftp_interact_line +{ + fstring cmd_refer; // pointer to packet payload + fstring arg_refer; // pointer to packet payload +}; + +struct ftp_parse_result +{ + /* all data is persistent, sync with data link, need be free when session close */ + int cursor; + struct iovec result_array[FTP_MSG_MAX]; + uint8_t push_result_flags[FTP_MSG_MAX]; +}; + +struct ftp_context +{ + enum ftp_link_type link_type; + enum ftp_msg_type data_link_presentation; // FTP_INVENTORY or FTP_FILE_CONTENT + struct ftp_interact_line cmd_result; + struct ftp_parse_result parse_result; + struct ftp_interact_line last_cmd; + struct ftp_link_key last_data_link_key; +}; + +struct ftp_interact_parser +{ + enum ftp_msg_type cmd_msg_type; + const char *cmd_name; + int cmd_len; + int (*cmd_handler)(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv); +}; + +struct ftp_stat +{ + struct fieldstat_easy *fs4_instance; + int fs4_counter_id[FTPD_STAT_MAX]; +}; + +struct ftp_decoder_env +{ + void *logger_handle; + int plugin_id; + int thread_count; + int tcp_sub_topic_id; + int ftp_pub_ctrl_topic_id; + int ftp_pub_data_topic_id; + void **data_link_table; // key is tuple3 for PASV, tuple4 for PORT + struct ftp_stat fstat; +}; + +struct ftp_message +{ + struct ftp_context *fctx; +}; + +extern "C" void *FTP_ONLOAD(struct stellar *st); +extern "C" void FTP_UNLOAD(void *plugin_env); +int ftp_hash_table_create(struct ftp_decoder_env *fenv); +void ftp_hash_table_destroy(struct ftp_decoder_env *fenv); +int ftp_ctrl_identify_by_payload(const char *payload, size_t len, u_int8_t curdir); +int ftp_ctrl_identify_by_addr(struct session *sess); +int ftp_ctrl_identify(struct session *sess, const char *payload, size_t len, u_int8_t curdir); +struct ftp_context *ftp_data_identify(struct session *sess, struct ftp_decoder_env *fenv); +int ftp_cmd_readline(struct ftp_interact_line *line, const char *payload, size_t len); +int ftp_cmd_process(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv, int curdir); +int ftp_decoder_push_msg(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv); +int ftp_parse_ipv4_port_style(const fstring *cmd_str, unsigned int *ipv4_net, unsigned short *port_net); +int ftp_parse_ipv6_port_style(const fstring *cmd_str, unsigned short *port_net); +int ftp_parse_eprt_ipport_style(const fstring *arg_str, struct in6_addr *ipd_addr, unsigned short *port_net);
\ No newline at end of file diff --git a/src/ftp_decoder_proto.cpp b/src/ftp_decoder_proto.cpp new file mode 100644 index 0000000..dd5f14a --- /dev/null +++ b/src/ftp_decoder_proto.cpp @@ -0,0 +1,566 @@ +#include "ftp_decoder_inner.h" +#include "ftp_decoder.h" +#include "ftp_decoder_util.h" +#include "ftp_decoder_hash.h" +#include <sys/types.h> +#include <arpa/inet.h> + +/* + ------------- + |/---------\| + || User || -------- + ||Interface|<--->| User | + |\----^----/| -------- + ---------- | | | + |/------\| FTP Commands |/----V----\| + ||Server|<---------------->| User || + || PI || FTP Replies || PI || + |\--^---/| |\----^----/| + | | | | | | + -------- |/--V---\| Data |/----V----\| -------- + | File |<--->|Server|<---------------->| User |<--->| File | + |System| || DTP || Connection || DTP || |System| + -------- |\------/| |\---------/| -------- + ---------- ------------- + + Server-FTP USER-FTP +*/ + +/* + ctrl link command data link tcp/ip stack event +--------------------------------------------------------------------------------------------------- + PORT 192,168,38,2,202,95 + listen tcp port: 51807 + 200 PORT command successful. + LIST + server use src tcp port 20 connecting to client tcp port 51807 + data connection is established + send 'ls -l' result to cliet + 226 Directory send OK. + close the tcp data connection + waiting for next command... +*/ +static const struct iovec G_FTP_EMPTY_IOV = {NULL, 0}; + +static struct ftp_context *ftp_get_declare_datalink_ctx(struct session *sess, struct ftp_context *ctrl_fctx, struct ftp_decoder_env *fenv) +{ + int thread_id = session_get_current_thread_id(sess); + struct ftp_context *declare_data_link_ftx = (struct ftp_context *)ftp_hash_search(fenv->data_link_table[thread_id], &ctrl_fctx->last_data_link_key); + if (NULL == declare_data_link_ftx) + { + ftp_runtime_log(RLOG_LV_FATAL, "declare data link not found, key=%s", ftp_hash_key_to_str(&ctrl_fctx->last_data_link_key)); + return NULL; + } + return declare_data_link_ftx; +} + +static void ftp_assemble_uri(struct session *sess, const struct iovec *raw_arg, struct iovec *uri) +{ + char dip_str[INET6_ADDRSTRLEN] = {}; + char tmp_url[2048] = {}; + enum session_addr_type saddr_type; + struct session_addr *saddr = session_get0_addr(sess, &saddr_type); + if (saddr_type == SESSION_ADDR_TYPE_IPV4_TCP) + { + inet_ntop(AF_INET, &saddr->ipv4.daddr, dip_str, INET_ADDRSTRLEN); + } + else + { + inet_ntop(AF_INET6, saddr->ipv6.daddr, dip_str, INET6_ADDRSTRLEN); + } + int ret = snprintf(tmp_url, sizeof(tmp_url), "ftp://%s/%.*s", dip_str, IOVEC_PRINT(*raw_arg)); + if (uri->iov_base) + { + free(uri->iov_base); + } + uri->iov_base = calloc(1, ret); + memcpy(uri->iov_base, tmp_url, ret); + uri->iov_len = ret; +} + +int ftp_cmd_handler_do_user(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + fstring_safe_dup(&fctx->cmd_result.arg_refer, &fctx->parse_result.result_array[FTP_ACCOUNT]); + ftp_runtime_log(RLOG_LV_DEBUG, "'%s': USER: %.*s", session_get0_readable_addr(sess), IOVEC_PRINT(fctx->parse_result.result_array[FTP_ACCOUNT])); + return 0; +} + +int ftp_cmd_handler_do_pass(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + fstring_safe_dup(&fctx->cmd_result.arg_refer, &fctx->parse_result.result_array[FTP_PASSWORD]); + ftp_runtime_log(RLOG_LV_DEBUG, "'%s': PASS: %.*s", session_get0_readable_addr(sess), IOVEC_PRINT(fctx->parse_result.result_array[FTP_PASSWORD])); + return 0; +} + +int ftp_parse_ipv4_port_style(const fstring *cmd_str, unsigned int *ipv4_net, unsigned short *port_net) +{ + unsigned int fields[6]; + char raw_cmd_str_tmp[cmd_str->iov_len + 1] = {}; + memcpy(raw_cmd_str_tmp, cmd_str->iov_base, cmd_str->iov_len); + char only_integer_str_tmp[cmd_str->iov_len + 1] = {}; + if (sscanf(raw_cmd_str_tmp, "%*[a-zA-Z (]%[0-9,]", only_integer_str_tmp) <= 0) + { + if (sscanf(raw_cmd_str_tmp, "%[1234567890,]", only_integer_str_tmp) <= 0) + { + ftp_runtime_log(RLOG_LV_FATAL, "ftp_parse_ipv4_port_style parse error: %.*s", IOVEC_PRINT_PTR(cmd_str)); + return -1; + } + } + int ret = sscanf(only_integer_str_tmp, "%u,%u,%u,%u,%u,%u", &fields[0], &fields[1], &fields[2], &fields[3], &fields[4], &fields[5]); + if (ret != 6) + { + ftp_runtime_log(RLOG_LV_FATAL, "ftp_parse_ipv4_port_style parse error: %.*s", cmd_str->iov_len, only_integer_str_tmp); + return -1; + } + unsigned int dst_ip_host = (fields[0] << 24) | (fields[1] << 16) | (fields[2] << 8) | fields[3]; + unsigned short dst_port_host = (fields[4] << 8) | fields[5]; + *ipv4_net = htonl(dst_ip_host); + *port_net = htons(dst_port_host); + return 0; +} +/* PORT command only support IPv4, arg pattern: h1,h2,h3,h4,p1,p2 */ +int ftp_cmd_handler_do_port(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + fstring_safe_dup(&fctx->cmd_result.cmd_refer, &fctx->parse_result.result_array[FTP_TRANS_MODE]); + fctx->parse_result.push_result_flags[FTP_TRANS_MODE] = 0; + + unsigned int dst_ip_net; + unsigned short dst_port_net; + if (ftp_parse_ipv4_port_style(&fctx->cmd_result.arg_refer, &dst_ip_net, &dst_port_net) < 0) + { + return -1; + } + enum session_addr_type saddr_type; + struct session_addr *saddr = session_get0_addr(sess, &saddr_type); + fctx->last_data_link_key.addr_type = SESSION_ADDR_TYPE_IPV4_TCP; + // in active mode, new data link src ip address is server ip address + ftp_make_hkey_v4(&fctx->last_data_link_key.tuplev4, saddr->ipv4.daddr, dst_ip_net, dst_port_net); + + struct ftp_context *new_data_link_ctx = ftp_decoder_context_deep_dup(fctx); + int thread_id = session_get_current_thread_id(sess); + ftp_hash_add(fenv->data_link_table[thread_id], &fctx->last_data_link_key.tuplev4, sizeof(struct session_addr_ipv4), new_data_link_ctx); + + ftp_decoder_stat_incrby(thread_id, fenv, FTPD_STAT_NEGOTIATE_DATA_LINK, 1); + + ftp_runtime_log(RLOG_LV_DEBUG, "do_port_cb: %.*s, parsed dip:%x, dport:%u", IOVEC_PRINT(fctx->parse_result.result_array[FTP_TRANS_MODE]), dst_ip_net, dst_port_net); + return 0; +} + +/* PASV command only support IPv4, the response like: '227 Entering Passive Mode (218,13,32,6,78,40).' */ +int ftp_cmd_handler_do_pasv(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + fstring_safe_dup(&fctx->cmd_result.cmd_refer, &fctx->parse_result.result_array[FTP_TRANS_MODE]); + fctx->parse_result.push_result_flags[FTP_TRANS_MODE] = 0; + ftp_runtime_log(RLOG_LV_DEBUG, "'%s' PASV command", session_get0_readable_addr(sess)); + return 0; +} + +int ftp_cmd_handler_do_list(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + fstring_safe_dup(&fctx->cmd_result.cmd_refer, &fctx->parse_result.result_array[FTP_TRANS_DIR]); + fstring_safe_dup(&G_FTP_EMPTY_IOV, &fctx->parse_result.result_array[FTP_URI]); + struct ftp_context *declare_data_link_ftx = ftp_get_declare_datalink_ctx(sess, fctx, fenv); + if (declare_data_link_ftx) + { + fstring_safe_dup(&fctx->cmd_result.cmd_refer, &declare_data_link_ftx->parse_result.result_array[FTP_TRANS_DIR]); + fstring_safe_dup(&G_FTP_EMPTY_IOV, &declare_data_link_ftx->parse_result.result_array[FTP_URI]); + declare_data_link_ftx->data_link_presentation = FTP_INVENTORY; + } + ftp_runtime_log(RLOG_LV_DEBUG, "'%s': LIST command", session_get0_readable_addr(sess)); + return 0; +} + +static void ftp_cmd_handler_do_stor_retr_common(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + fstring_safe_dup(&fctx->cmd_result.cmd_refer, &fctx->parse_result.result_array[FTP_TRANS_DIR]); + ftp_assemble_uri(sess, &fctx->cmd_result.arg_refer, &fctx->parse_result.result_array[FTP_URI]); + fctx->parse_result.push_result_flags[FTP_TRANS_DIR] = 0; + fctx->parse_result.push_result_flags[FTP_URI] = 0; + + struct ftp_context *declare_data_link_ftx = ftp_get_declare_datalink_ctx(sess, fctx, fenv); + if (declare_data_link_ftx) + { + declare_data_link_ftx->data_link_presentation = FTP_FILE_CONTENT; + fstring_safe_dup(&fctx->parse_result.result_array[FTP_TRANS_DIR], &declare_data_link_ftx->parse_result.result_array[FTP_TRANS_DIR]); + fstring_safe_dup(&fctx->parse_result.result_array[FTP_URI], &declare_data_link_ftx->parse_result.result_array[FTP_URI]); + // ftp_update_parse_result(declare_data_link_ftx, &fctx->parse_result.result_array[FTP_URI], FTP_URI); + } +} + +int ftp_cmd_handler_do_stor(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + ftp_cmd_handler_do_stor_retr_common(sess, fctx, fenv); + ftp_runtime_log(RLOG_LV_DEBUG, "'%s', STOR uri:%.*s", session_get0_readable_addr(sess), IOVEC_PRINT(fctx->parse_result.result_array[FTP_URI])); + return 0; +} + +int ftp_cmd_handler_do_retr(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + ftp_cmd_handler_do_stor_retr_common(sess, fctx, fenv); + ftp_runtime_log(RLOG_LV_DEBUG, "'%s', RETR uri:%.*s", session_get0_readable_addr(sess), IOVEC_PRINT(fctx->parse_result.result_array[FTP_URI])); + return 0; +} + +int ftp_parse_eprt_ipport_style(const fstring *arg_str, struct in6_addr *ipd_addr, unsigned short *port_net) +{ + unsigned int port_host; + int inet_proto; + char ip6_addr_str[INET6_ADDRSTRLEN] = {}; + char raw_cmd_str_tmp[arg_str->iov_len + 1] = {}; + memcpy(raw_cmd_str_tmp, arg_str->iov_base, arg_str->iov_len); + + char *save_ptr, *ptr; + const char *delim = " |\t"; + ptr = strtok_r(raw_cmd_str_tmp, delim, &save_ptr); + if (NULL == ptr) + { + ftp_runtime_log(RLOG_LV_FATAL, "ftp_parse_eprt_ipport_style parse error: %.*s", IOVEC_PRINT_PTR(arg_str)); + return -1; + } + inet_proto = atoi(ptr); + if (2 != inet_proto) + { + ftp_runtime_log(RLOG_LV_FATAL, "ftp_parse_eprt_ipport_style parse error: %.*s, not support inet: %d", IOVEC_PRINT_PTR(arg_str), inet_proto); + return -1; + } + ptr = strtok_r(NULL, delim, &save_ptr); + if (NULL == ptr) + { + ftp_runtime_log(RLOG_LV_FATAL, "ftp_parse_eprt_ipport_style parse error: %.*s", IOVEC_PRINT_PTR(arg_str)); + return -1; + } + strncpy(ip6_addr_str, ptr, INET6_ADDRSTRLEN - 1); + ptr = strtok_r(NULL, delim, &save_ptr); + if (NULL == ptr) + { + ftp_runtime_log(RLOG_LV_FATAL, "ftp_parse_eprt_ipport_style parse error: %.*s", IOVEC_PRINT_PTR(arg_str)); + return -1; + } + port_host = (unsigned int)atoi(ptr); + + while (strtok_r(NULL, "|", &save_ptr)) + ; + inet_pton(AF_INET6, ip6_addr_str, ipd_addr); + *port_net = htons((unsigned short)port_host); + return 0; +} + +/* EPRT support IPv4 and IPv6, pattern: EPRT<space><d><net-prt><d><net-addr><d><tcp-port><d> + example: + EPRT |1|132.235.1.2|6275| + EPRT |2|1080::8:800:200C:417A|5282| + refer: https://datatracker.ietf.org/doc/html/rfc2428#section-2 +*/ +int ftp_cmd_handler_do_eprt(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + struct in6_addr ipd_addr; + unsigned short port_net; + fstring_safe_dup(&fctx->cmd_result.cmd_refer, &fctx->parse_result.result_array[FTP_TRANS_MODE]); + if (ftp_parse_eprt_ipport_style(&fctx->cmd_result.arg_refer, &ipd_addr, &port_net) < 0) + { + return -1; + } + + enum session_addr_type saddr_type; + struct session_addr *saddr = session_get0_addr(sess, &saddr_type); + fctx->last_data_link_key.addr_type = SESSION_ADDR_TYPE_IPV6_TCP; + // in active mode, new data link src ip address is server ip address + ftp_make_hkey_v6(&fctx->last_data_link_key.tuplev6, (struct in6_addr *)&saddr->ipv6.daddr, &ipd_addr, port_net); + + struct ftp_context *new_data_link_ctx = ftp_decoder_context_deep_dup(fctx); + int thread_id = session_get_current_thread_id(sess); + ftp_hash_add(fenv->data_link_table[thread_id], &fctx->last_data_link_key.tuplev6, sizeof(struct session_addr_ipv6), new_data_link_ctx); + + ftp_decoder_stat_incrby(thread_id, fenv, FTPD_STAT_NEGOTIATE_DATA_LINK, 1); + + ftp_runtime_log(RLOG_LV_DEBUG, "'%s': EPRT command, port:%u", session_get0_readable_addr(sess), ntohs(port_net)); + return 0; +} + +/* LPRT support IPv4 and IPv6, pattern: LPRT af,hal,h1,h2,h3,h4...,pal,p1,p2... + example: + LPRT 6,16,32,2,81,131,67,131,0,0,0,0,0,0,81,131,67,131,2,4,7 + refer: https://www.rfc-editor.org/rfc/rfc1639.html +*/ +int ftp_cmd_handler_do_lprt(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + // todo + ftp_runtime_log(RLOG_LV_INFO, "LPRT command not support yet!"); + return -1; +} + +/* EPSV support IPv4 and IPv6, + refer: https://datatracker.ietf.org/doc/html/rfc2428#autoid-3 +*/ +int ftp_cmd_handler_do_epsv(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + // todo + ftp_runtime_log(RLOG_LV_INFO, "EPSV command not support yet!"); + return -1; +} + +/* LPSV support IPv4 and IPv6, response is 228 xxxx... + refer: https://www.rfc-editor.org/rfc/rfc1639.html +*/ +int ftp_cmd_handler_do_lpsv(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + // todo + ftp_runtime_log(RLOG_LV_INFO, "LPSV command not support yet!"); + return -1; +} + +static const struct ftp_interact_parser g_ftp_client_cmd_tuple[] = + { + {FTP_ACCOUNT, "USER", 4, ftp_cmd_handler_do_user}, + {FTP_PASSWORD, "PASS", 4, ftp_cmd_handler_do_pass}, + {FTP_TRANS_MODE, "PORT", 4, ftp_cmd_handler_do_port}, + {FTP_TRANS_MODE, "PASV", 4, ftp_cmd_handler_do_pasv}, + {FTP_TRANS_MODE, "EPRT", 4, ftp_cmd_handler_do_eprt}, + {FTP_TRANS_MODE, "EPSV", 4, ftp_cmd_handler_do_epsv}, + {FTP_TRANS_MODE, "LPRT", 4, ftp_cmd_handler_do_lprt}, + {FTP_TRANS_MODE, "LPSV", 4, ftp_cmd_handler_do_lpsv}, + {FTP_MSG_MAX, "LIST", 4, ftp_cmd_handler_do_list}, + {FTP_TRANS_DIR, "STOR", 4, ftp_cmd_handler_do_stor}, + {FTP_TRANS_DIR, "RETR", 4, ftp_cmd_handler_do_retr}, + {FTP_MSG_MAX, NULL, 0, NULL}}; + +int ftp_res_handler_do_220(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + fstring_safe_dup(&fctx->cmd_result.arg_refer, &fctx->parse_result.result_array[FTP_BANNER]); + return 0; +} + +int ftp_res_handler_do_200(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + // todo + ftp_runtime_log(RLOG_LV_INFO, "'%s': response 200 command not support yet!", session_get0_readable_addr(sess)); + return -1; +} + +/* + example: 227 Entering Passive Mode (218,13,32,6,78,40). + */ +int ftp_res_handler_do_227(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + unsigned int dst_ip_net; + unsigned short dst_port_net; + if (ftp_parse_ipv4_port_style(&fctx->cmd_result.arg_refer, &dst_ip_net, &dst_port_net) < 0) + { + return -1; + } + enum session_addr_type saddr_type; + struct session_addr *saddr = session_get0_addr(sess, &saddr_type); + fctx->last_data_link_key.addr_type = SESSION_ADDR_TYPE_IPV4_TCP; + // in passive mode, new data link src ip address is client ip address + ftp_make_hkey_v4(&fctx->last_data_link_key.tuplev4, saddr->ipv4.saddr, dst_ip_net, dst_port_net); + + struct ftp_context *new_data_link_ctx = ftp_decoder_context_deep_dup(fctx); + int thread_id = session_get_current_thread_id(sess); + ftp_hash_add(fenv->data_link_table[thread_id], &fctx->last_data_link_key.tuplev4, sizeof(struct session_addr_ipv4), new_data_link_ctx); + ftp_decoder_stat_incrby(thread_id, fenv, FTPD_STAT_NEGOTIATE_DATA_LINK, 1); + ftp_runtime_log(RLOG_LV_DEBUG, "'%s': response 227, %.*s parsed dip:0x%x, dport:%u", session_get0_readable_addr(sess), IOVEC_PRINT(fctx->cmd_result.arg_refer), ntohl(dst_ip_net), ntohs(dst_port_net)); + return 0; +} + +/* + example: 228 Entering Long Passive Mode (af, hal, h1, h2, h3,..., pal, p1, p2...) + refer: https://www.rfc-editor.org/rfc/rfc1639.html + */ +int ftp_res_handler_do_228(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + // todo + ftp_runtime_log(RLOG_LV_INFO, "'%s': response 228 command not support yet!", session_get0_readable_addr(sess)); + return -1; +} + +int ftp_parse_ipv6_port_style(const fstring *cmd_str, unsigned short *port_net) +{ + unsigned int port_host; + char raw_cmd_str_tmp[cmd_str->iov_len + 1] = {}; + memcpy(raw_cmd_str_tmp, cmd_str->iov_base, cmd_str->iov_len); + char only_integer_str_tmp[cmd_str->iov_len + 1] = {}; + if (sscanf(raw_cmd_str_tmp, "%*[a-zA-Z (|]%[0-9]", only_integer_str_tmp) <= 0) + { + if (sscanf(raw_cmd_str_tmp, "|||%s|", only_integer_str_tmp) <= 0) + { + ftp_runtime_log(RLOG_LV_FATAL, "ftp_parse_ipv4_port_style parse error: %.*s", IOVEC_PRINT_PTR(cmd_str)); + return -1; + } + } + int ret = sscanf(only_integer_str_tmp, "%u", &port_host); + if (ret != 1) + { + ftp_runtime_log(RLOG_LV_FATAL, "ftp_parse_ipv6_port_style parse error: %.*s", cmd_str->iov_len, only_integer_str_tmp); + return -1; + } + *port_net = htons((unsigned short)port_host); + return 0; +} +/* + example: 229 229 Entering Extended Passive Mode (|||20987|) + refer: https://datatracker.ietf.org/doc/html/rfc2428#autoid-3 + */ +int ftp_res_handler_do_229(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv) +{ + unsigned short port_net; + if (ftp_parse_ipv6_port_style(&fctx->cmd_result.arg_refer, &port_net) < 0) + { + ftp_runtime_log(RLOG_LV_FATAL, "'%s': response 229 parse error, %.*s", session_get0_readable_addr(sess), IOVEC_PRINT(fctx->cmd_result.arg_refer)); + return -1; + } + + enum session_addr_type saddr_type; + struct session_addr *saddr = session_get0_addr(sess, &saddr_type); + if (saddr_type != SESSION_ADDR_TYPE_IPV6_TCP) + { + ftp_runtime_log(RLOG_LV_FATAL, "'%s': response 229, but addr type is not ipv6 %.*s", session_get0_readable_addr(sess), IOVEC_PRINT(fctx->cmd_result.arg_refer)); + return -1; + } + fctx->last_data_link_key.addr_type = SESSION_ADDR_TYPE_IPV6_TCP; + // in passive mode, new data link src ip address is client ip address + ftp_make_hkey_v6(&fctx->last_data_link_key.tuplev6, (struct in6_addr *)saddr->ipv6.saddr, (struct in6_addr *)saddr->ipv6.daddr, port_net); + + struct ftp_context *new_data_link_ctx = ftp_decoder_context_deep_dup(fctx); + int thread_id = session_get_current_thread_id(sess); + ftp_hash_add(fenv->data_link_table[thread_id], &fctx->last_data_link_key.tuplev6, sizeof(struct session_addr_ipv6), new_data_link_ctx); + ftp_decoder_stat_incrby(thread_id, fenv, FTPD_STAT_NEGOTIATE_DATA_LINK, 1); + ftp_runtime_log(RLOG_LV_DEBUG, "'%s': EPSV response 229, port is:%u", session_get0_readable_addr(sess), ntohs(port_net)); + return -1; +} + +static const struct ftp_interact_parser g_ftp_server_response_tuple[] = + { + {FTP_BANNER, "220", 3, ftp_res_handler_do_220}, + {FTP_MSG_MAX, "200", 3, ftp_res_handler_do_200}, + {FTP_MSG_MAX, "227", 3, ftp_res_handler_do_227}, + {FTP_MSG_MAX, "228", 3, ftp_res_handler_do_228}, + {FTP_MSG_MAX, "229", 3, ftp_res_handler_do_229}, + {FTP_MSG_MAX, NULL, 0, NULL}}; + +int ftp_ctrl_identify_by_payload(const char *payload, size_t len, u_int8_t curdir) +{ + if (NULL == payload || len < FTPD_IDENTIRY_MIN_LEN) + { + return 0; + } + if (curdir == PACKET_DIRECTION_C2S) + { + if (memcmp(payload, "USER", 4) == 0) + { + return 1; + } + } + else + { + if (memcmp(payload, "220", 3) == 0) + { + int tmplen = MIN(len, FTPD_IDENTIRY_MAX_LEN); + char tmp_buffer[tmplen]; + memcpy(tmp_buffer, payload, tmplen); + ftp_strtolower(tmp_buffer, tmplen); + if ((memmem(tmp_buffer, tmplen, "ftp", 3) != NULL)) + { + return 1; + } + } + } + return 0; +} + +int ftp_ctrl_identify_by_addr(struct session *sess) +{ + enum session_addr_type saddr_type = SESSION_ADDR_TYPE_UNKNOWN; + struct session_addr *saddr = session_get0_addr(sess, &saddr_type); + unsigned short sport_host = 0, dport_host = 0; + if (saddr_type == SESSION_ADDR_TYPE_IPV4_TCP) + { + sport_host = ntohs(saddr->ipv4.sport); + dport_host = ntohs(saddr->ipv4.dport); + } + else if (saddr_type == SESSION_ADDR_TYPE_IPV6_TCP) + { + sport_host = ntohs(saddr->ipv6.sport); + dport_host = ntohs(saddr->ipv6.dport); + } + else + { + return 0; + } + if (sport_host == 21 || dport_host == 21) + { + return 1; + } + if (sport_host == 25 || dport_host == 25) + { + return 0; + } + if (sport_host == 110 || dport_host == 110) + { + return 0; + } + return 0; +} + +int ftp_ctrl_identify(struct session *sess, const char *payload, size_t len, u_int8_t curdir) +{ + if (ftp_ctrl_identify_by_addr(sess)) + { + return 1; + } + return ftp_ctrl_identify_by_payload(payload, len, curdir); +} + +struct ftp_context *ftp_data_identify(struct session *sess, struct ftp_decoder_env *fenv) +{ + struct ftp_link_key hash_key = {}; + if (ftp_build_hashkey_from_session(sess, &hash_key) < 0) + { + return NULL; + } + int thread_id = session_get_current_thread_id(sess); + struct ftp_context *declare_fctx = (struct ftp_context *)ftp_hash_search(fenv->data_link_table[thread_id], &hash_key); + if (NULL == declare_fctx) + { + return NULL; + } + // use tuple3 as hash key, so free it immediately after found! + ftp_hash_del(fenv->data_link_table[thread_id], &hash_key); + return declare_fctx; +} + +static const struct ftp_interact_parser *ftp_fetch_cmd_parser(const struct ftp_interact_parser *table, const struct ftp_interact_line *cmd_result) +{ + for (int i = 0; table[i].cmd_name != NULL; i++) + { + if (0 == strncmp(table[i].cmd_name, (char *)cmd_result->cmd_refer.iov_base, table[i].cmd_len)) + { + return &table[i]; + } + } + return NULL; +} + +int ftp_cmd_process(struct session *sess, struct ftp_context *fctx, struct ftp_decoder_env *fenv, int curdir) +{ + const struct ftp_interact_parser *parser; + if (PACKET_DIRECTION_C2S == curdir) + { + parser = ftp_fetch_cmd_parser(g_ftp_client_cmd_tuple, &fctx->cmd_result); + } + else + { + parser = ftp_fetch_cmd_parser(g_ftp_server_response_tuple, &fctx->cmd_result); + } + if (NULL == parser) + { + ftp_runtime_log(RLOG_LV_INFO, "ftp_fetch_cmd_parser() failed, '%s', not support cmd %.*s", session_get0_readable_addr(sess), IOVEC_PRINT(fctx->cmd_result.cmd_refer)); + return -1; + } + if (parser->cmd_handler(sess, fctx, fenv) != -1) + { + fctx->parse_result.push_result_flags[parser->cmd_msg_type] = 0; + ftp_decoder_push_msg(sess, fctx, fenv); + ftp_decoder_stat_incrby(session_get_current_thread_id(sess), fenv, FTPD_STAT_CTRL_CMD, 1); + } + return 0; +} diff --git a/src/ftp_decoder_stat.cpp b/src/ftp_decoder_stat.cpp new file mode 100644 index 0000000..ac5f1ce --- /dev/null +++ b/src/ftp_decoder_stat.cpp @@ -0,0 +1,54 @@ +#include <assert.h> +#include <fieldstat/fieldstat_easy.h> +#include "ftp_decoder_stat.h" +#include "ftp_decoder_inner.h" +#include "ftp_decoder_util.h" + +static const struct ftpd_stat_config_tuple g_ftpd_stat_tuple[] = + { + {FTPD_STAT_CTRL_LINK_OPEN, "clink_open"}, + {FTPD_STAT_CTRL_LINK_CLOSE, "clink_close"}, + {FTPD_STAT_DATA_LINK_OPEN, "dlink_open"}, + {FTPD_STAT_DATA_LINK_CLOSE, "dlink_close"}, + {FTPD_STAT_NEGOTIATE_DATA_LINK, "negotiate_dlink"}, + {FTPD_STAT_CTRL_LINK_BYTES_C2S, "clink_bytes_c2s"}, + {FTPD_STAT_CTRL_LINK_BYTES_S2C, "clink_bytes_s2c"}, + {FTPD_STAT_DATA_LINK_BYTES_C2S, "dlink_bytes_c2s"}, + {FTPD_STAT_DATA_LINK_BYTES_S2C, "dlink_bytes_s2c"}, + {FTPD_STAT_CTRL_CMD, "ctrl_cmd"}, +}; + +void ftp_decoder_stat_incrby(int thread_idx, struct ftp_decoder_env *fenv, enum ftp_decoder_stat_type stattype, long long increment) +{ + if (fenv->fstat.fs4_instance) + { + fieldstat_easy_counter_incrby(fenv->fstat.fs4_instance, thread_idx, fenv->fstat.fs4_counter_id[stattype], NULL, 0, increment); + } +} + +int ftp_decoder_stat_init(struct ftp_decoder_env *fenv) +{ + assert(sizeof(g_ftpd_stat_tuple) / sizeof(struct ftpd_stat_config_tuple) == FTPD_STAT_MAX); + fenv->fstat.fs4_instance = fieldstat_easy_new(fenv->thread_count, FTP_DECODER_FIELDSTAT_NAME, NULL, 0); + + for (int i = 0; i < FTPD_STAT_MAX; i++) + { + fenv->fstat.fs4_counter_id[i] = fieldstat_easy_register_counter(fenv->fstat.fs4_instance, g_ftpd_stat_tuple[i].name); + } + ftp_mkdir_p("./metrics", 0644); + int ret = fieldstat_easy_enable_auto_output(fenv->fstat.fs4_instance, FTP_DECODER_FIELDSTAT_OUTPUT_FILE, FTP_DECODER_FIELDSTAT_OUTPUT_INTERVAL); + if (ret < 0) + { + fprintf(stderr, "fieldstat_easy_enable_auto_output '%s' failed\n", FTP_DECODER_FIELDSTAT_OUTPUT_FILE); + fieldstat_easy_free(fenv->fstat.fs4_instance); + fenv->fstat.fs4_instance = NULL; + return -1; + } + return 0; +} + +void ftp_decoder_stat_free(struct ftp_decoder_env *fenv) +{ + fieldstat_easy_free(fenv->fstat.fs4_instance); + fenv->fstat.fs4_instance = NULL; +}
\ No newline at end of file diff --git a/src/ftp_decoder_stat.h b/src/ftp_decoder_stat.h new file mode 100644 index 0000000..bfca60c --- /dev/null +++ b/src/ftp_decoder_stat.h @@ -0,0 +1,28 @@ +#pragma once +#include <fieldstat/fieldstat_easy.h> + +enum ftp_decoder_stat_type{ + FTPD_STAT_CTRL_LINK_OPEN = 0, + FTPD_STAT_CTRL_LINK_CLOSE, + FTPD_STAT_DATA_LINK_OPEN, + FTPD_STAT_DATA_LINK_CLOSE, + FTPD_STAT_NEGOTIATE_DATA_LINK, //Negotiating by PORT or PASV command in ctrl link + FTPD_STAT_CTRL_LINK_BYTES_C2S, + FTPD_STAT_CTRL_LINK_BYTES_S2C, + FTPD_STAT_DATA_LINK_BYTES_C2S, + FTPD_STAT_DATA_LINK_BYTES_S2C, + FTPD_STAT_CTRL_CMD, + + FTPD_STAT_MAX, +}; + + +struct ftpd_stat_config_tuple +{ + enum ftp_decoder_stat_type type; + const char *name; +}; + +int ftp_decoder_stat_init(struct ftp_decoder_env *fenv); +void ftp_decoder_stat_free(struct ftp_decoder_env *fenv); +void ftp_decoder_stat_incrby(int thread_idx, struct ftp_decoder_env *fenv, enum ftp_decoder_stat_type stattype, long long increment);
\ No newline at end of file diff --git a/src/ftp_decoder_util.cpp b/src/ftp_decoder_util.cpp new file mode 100644 index 0000000..8956887 --- /dev/null +++ b/src/ftp_decoder_util.cpp @@ -0,0 +1,163 @@ +#include "ftp_decoder_inner.h" +#include "ftp_decoder.h" +#include "ftp_decoder_util.h" +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <libgen.h> + +void fstring_dup(const fstring *src, fstring *dst) +{ + dst->iov_base = (char *)malloc(src->iov_len); + assert(dst->iov_base); + memcpy((void *)dst->iov_base, src->iov_base, src->iov_len); + dst->iov_len = src->iov_len; +} +void fstring_safe_dup(const fstring *src, fstring *dst) +{ + if (dst->iov_base) + { + free(dst->iov_base); // free it if exist + } + fstring_dup(src, dst); +} + +void ftp_parse_result_free(struct ftp_parse_result *parse_result) +{ + for (int i = 0; i < FTP_MSG_MAX; i++) + { + if (i != FTP_INVENTORY && i != FTP_FILE_CONTENT) + { + if (parse_result->result_array[i].iov_base) + { + free(parse_result->result_array[i].iov_base); + } + } + } +} + +static void ftp_str_split(const char *payload, size_t len, fstring *left, fstring *rigth, int delim) +{ + const char *p = (char *)memchr(payload, delim, len); + if (NULL == p) + { + left->iov_base = (char *)payload; + left->iov_len = len; + rigth->iov_base = NULL; + rigth->iov_len = 0; + } + else + { + left->iov_base = (char *)payload; + left->iov_len = p - payload; + rigth->iov_base = (char *)p + 1; + rigth->iov_len = len - left->iov_len - 1; + } +} + +long ftp_skip_tail_crlf(const char *payload, long len) +{ + long new_len = 0; + for (long i = 0; i < len; i++, new_len++) + { + if ((payload[i] == '\r' || payload[i] == '\n')) + { + break; + } + } + return new_len; +} + +int ftp_cmd_readline(struct ftp_interact_line *line, const char *payload, size_t len) +{ + memset(line, 0, sizeof(struct ftp_interact_line)); + long skip_crlf_len = ftp_skip_tail_crlf(payload, len); + ftp_str_split(payload, skip_crlf_len, &line->cmd_refer, &line->arg_refer, ' '); + return 0; +} + +void ftp_strtolower(char *str, size_t len) +{ + for (size_t i = 0; i < len; i++) + { + str[i] = tolower(str[i]); + } +} + +int ftp_mkdir_p(const char *path, mode_t mode) +{ + struct stat st; + errno = 0; + + /* Try to make the directory */ + if (mkdir(path, mode) == 0) + return 0; + + /* If it fails for any reason but EEXIST, fail */ + if (errno != EEXIST) + return -1; + + /* Check if the existing path is a directory */ + if (stat(path, &st) != 0) + return -1; + + /* If not, fail with ENOTDIR */ + if (!S_ISDIR(st.st_mode)) + { + errno = ENOTDIR; + return -1; + } + + errno = 0; + return 0; +} + +int ftp_session_get_pkt_dir(struct session *sess) +{ + const struct packet *pkt = session_get0_current_packet(sess); + if (NULL == pkt) + { + return PACKET_DIRECTION_UNKNOWN; + } + return packet_get_direction(pkt); +} + +struct ftp_context *ftp_decoder_context_deep_dup(const struct ftp_context *src) +{ + struct ftp_context *new_ctx = (struct ftp_context *)calloc(1, sizeof(struct ftp_context)); + for (int i = 0; i < FTP_MSG_MAX; i++) + { + if (i != FTP_INVENTORY && i != FTP_FILE_CONTENT) + { + fstring_safe_dup(&src->parse_result.result_array[i], &new_ctx->parse_result.result_array[i]); + } + } + return new_ctx; +} + +const char *ftp_message_type_to_string(enum ftp_msg_type fmsg_type) +{ + switch (fmsg_type) + { + case FTP_BANNER: + return "FTP_BANNER"; + case FTP_ACCOUNT: + return "FTP_ACCOUNT"; + case FTP_PASSWORD: + return "FTP_PASSWORD"; + case FTP_URI: + return "FTP_URI"; + case FTP_TRANS_MODE: + return "FTP_TRANS_MODE"; + case FTP_TRANS_DIR: + return "FTP_TRANS_DIR"; + case FTP_INVENTORY: + return "FTP_INVENTORY"; + case FTP_FILE_CONTENT: + return "FTP_FILE_CONTENT"; + case FTP_MSG_MAX: + return "UNKNOWN"; + } + return "UNKNOWN"; +}
\ No newline at end of file diff --git a/src/ftp_decoder_util.h b/src/ftp_decoder_util.h new file mode 100644 index 0000000..4e83128 --- /dev/null +++ b/src/ftp_decoder_util.h @@ -0,0 +1,25 @@ +#pragma once +#include <sys/stat.h> +#include <sys/types.h> +#include <string.h> +#include <bits/types/struct_iovec.h> + +#ifndef MAX +#define MAX(a, b) ((a) >= (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a, b) ((a) >= (b) ? (b) : (a)) +#endif + +#ifndef fstring +typedef struct iovec fstring; +#endif + +void fstring_dup(const fstring *src, fstring *dst); +void fstring_safe_dup(const fstring *src, fstring *dst); +void ftp_strtolower(char *str, size_t len); +int ftp_mkdir_p(const char *path, mode_t mode); +int ftp_session_get_pkt_dir(struct session *sess); +struct ftp_context *ftp_decoder_context_deep_dup(const struct ftp_context *src); +long ftp_skip_tail_crlf(const char *payload, long len); +void ftp_parse_result_free(struct ftp_parse_result *parse_result);
\ No newline at end of file diff --git a/src/version.map b/src/version.map new file mode 100644 index 0000000..404c631 --- /dev/null +++ b/src/version.map @@ -0,0 +1,9 @@ +VERS_3.0{ +global: + extern "C" { + FTP_ONLOAD; + FTP_UNLOAD; + ftp_message_*; + }; + local: *; +}; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..6b76516 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required (VERSION 2.8...3.10) +set(DECODER_NAME ftp_decoder) +set(TEST_RUN_DIR ${CMAKE_INSTALL_PREFIX}/sapp) +set(SAPP_DEVEL_DIR ${TEST_RUN_DIR}/lib) +set(TEST_MAIN plugin_test_main) + +set(TEST_JSON_DIR ${CMAKE_SOURCE_DIR}/test/test_result_json) +set(TEST_PCAP_DIR ${CMAKE_SOURCE_DIR}/test/ftp_pcap) + +add_library(${lib_name}_test_plug SHARED ${lib_name}_test_plug.cpp) +target_link_libraries(${lib_name}_test_plug MESA_prof_load cjson) +set_target_properties(${lib_name}_test_plug PROPERTIES PREFIX "") + +include_directories(${CMAKE_SOURCE_DIR}/include/) +include_directories(${CMAKE_SOURCE_DIR}/src/) + +file(GLOB DEP_SRC "${CMAKE_SOURCE_DIR}/src/*.cpp") + +add_executable(gtest_ftp_decoder gtest_ftp_decoder.cpp ${DEP_SRC}) +target_link_libraries(gtest_ftp_decoder gtest fieldstat4 MESA_handle_logger MESA_prof_load MESA_htable) + +#build test env +add_test(NAME FTP_COPY_SPEC COMMAND sh -c "mkdir -p ${TEST_RUN_DIR}/stellar_plugin/ && cp ${CMAKE_SOURCE_DIR}/bin/ftp_decoder.inf ${TEST_RUN_DIR}/stellar_plugin/spec.toml") +add_test(NAME FTP_COPY_CONF COMMAND sh -c "mkdir -p ${TEST_RUN_DIR}/conf/ftp_decoder && cp ${CMAKE_SOURCE_DIR}/bin/ftp.conf ${TEST_RUN_DIR}/conf/ftp_decoder/ftp.conf") +add_test(NAME COPY_PLUGIN_TEST_MAIN COMMAND sh -c "rpm -ql sapp | grep plugin_test_main | xargs -i cp -f {} ${TEST_RUN_DIR}/") +add_test(NAME UPDATE_SAPP_LOG_LEVEL COMMAND bash -c "sed -i 's/sapp_log.fatal/sapp_log.info/' ${TEST_RUN_DIR}/etc/sapp_log.conf") + +# copy plugin lib to be tested +add_test(NAME FTP_COPY_DECODER_SO COMMAND sh -c "mkdir -p ${TEST_RUN_DIR}/stellar_plugin/${DECODER_NAME} && cp ${CMAKE_BINARY_DIR}/src/${DECODER_NAME}.so ${TEST_RUN_DIR}/stellar_plugin/${DECODER_NAME}/${DECODER_NAME}.so") +add_test(NAME FTP_COPY_TEST_PLUG_SO COMMAND sh -c "cp ${CMAKE_BINARY_DIR}/test/${DECODER_NAME}_test_plug.so ${TEST_RUN_DIR}/stellar_plugin/${DECODER_NAME}/${DECODER_NAME}_test_plug.so") + +set_tests_properties(FTP_COPY_SPEC FTP_COPY_CONF COPY_PLUGIN_TEST_MAIN UPDATE_SAPP_LOG_LEVEL + FTP_COPY_DECODER_SO FTP_COPY_TEST_PLUG_SO + PROPERTIES FIXTURES_SETUP TestFixture) + +# run tests +add_test(NAME FTP_TEST_V4_PORT COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/01-ftp-port-upload-download.json + -r ${TEST_PCAP_DIR}/01-ftp-port-upload-download.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) +add_test(NAME FTP_TEST_V4_PORT_C2S COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/01-ftp-port-upload-download_C2S.json + -r ${TEST_PCAP_DIR}/01-ftp-port-upload-download_C2S.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) +add_test(NAME FTP_TEST_V4_PORT_S2C COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/01-ftp-port-upload-download_S2C.json + -r ${TEST_PCAP_DIR}/01-ftp-port-upload-download_S2C.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) +add_test(NAME FTP_TEST_V4_NO_BANNER COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/04-ftp-banner-no-ftp-characters.json + -r ${TEST_PCAP_DIR}/04-ftp-banner-no-ftp-characters.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) +add_test(NAME FTP_TEST_V4_ONLY_CTRL COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/05-only-ctrl-link.json + -r ${TEST_PCAP_DIR}/05-only-ctrl-link.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) +add_test(NAME FTP_TEST_V4_PASV COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/06-ftp_pasv-upload-download.json + -r ${TEST_PCAP_DIR}/06-ftp_pasv-upload-download.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) +add_test(NAME FTP_TEST_V4_PASV_C2S COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/06-ftp_pasv-upload-download_C2S.json + -r ${TEST_PCAP_DIR}/06-ftp_pasv-upload-download_C2S.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) +add_test(NAME FTP_TEST_V4_PASV_S2C COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/06-ftp_pasv-upload-download_S2C.json + -r ${TEST_PCAP_DIR}/06-ftp_pasv-upload-download_S2C.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) + +add_test(NAME FTP_TEST_V6_1 COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/02-ftp_v6_1.json + -r ${TEST_PCAP_DIR}/02-ftp_v6_1.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) +add_test(NAME FTP_TEST_V6_2 COMMAND ./${TEST_MAIN} ${TEST_JSON_DIR}/03-ipv6_eport_upload_download.json + -r ${TEST_PCAP_DIR}/03-ipv6_eport_upload_download.pcap WORKING_DIRECTORY ${TEST_RUN_DIR}) +set_tests_properties(FTP_TEST_V4_PORT + FTP_TEST_V4_PORT_C2S + FTP_TEST_V4_PORT_S2C + FTP_TEST_V4_NO_BANNER + FTP_TEST_V4_ONLY_CTRL + FTP_TEST_V4_PASV + FTP_TEST_V4_PASV_C2S + FTP_TEST_V4_PASV_S2C + FTP_TEST_V6_1 + FTP_TEST_V6_2 + PROPERTIES FIXTURES_REQUIRED TestFixture)
\ No newline at end of file diff --git a/test/ftp_decoder_test_plug.cpp b/test/ftp_decoder_test_plug.cpp new file mode 100644 index 0000000..7e51544 --- /dev/null +++ b/test/ftp_decoder_test_plug.cpp @@ -0,0 +1,117 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#include "cJSON.h" + +#ifdef __cplusplus +extern "C" +{ +#endif +#include "ftp_decoder.h" +#include <stellar/stellar.h> +#include <stellar/session.h> +#include <stellar/session_mq.h> +#include <stellar/session_exdata.h> +extern int commit_test_result_json(cJSON *node, const char *name); +#ifdef __cplusplus +} +#endif + +static int g_result_count = 0; +struct ftp_test_context +{ + cJSON *json_root; + size_t tot_file_len; + int cmd_index; +}; + +void ftp_iovec_add_to_json(cJSON *object, const char *key, const struct iovec *fmsg_value, int cmd_index) +{ + char *tmp = (char *)calloc(1, fmsg_value->iov_len + 1); + memcpy(tmp, fmsg_value->iov_base, fmsg_value->iov_len); + char key_with_index[128] = {}; + snprintf(key_with_index, sizeof(key_with_index), "%s_%d", key, cmd_index); + cJSON_AddStringToObject(object, key_with_index, tmp); + free(tmp); + return ; +} + +extern "C" void *ftp_test_plug_session_ctx_new_cb(struct session *sess, void *plugin_env) +{ + struct ftp_test_context *fctx = (struct ftp_test_context *)calloc(1, sizeof(struct ftp_test_context)); + fctx->json_root = cJSON_CreateObject(); + return fctx; +} + +extern "C" void ftp_test_session_ctx_free_cb(struct session *sess, void *session_ctx, void *plugin_env) +{ + struct ftp_test_context *fctx = (struct ftp_test_context *)session_ctx; + if(fctx->tot_file_len > 0){ + cJSON_AddNumberToObject(fctx->json_root, "total-payload-len", fctx->tot_file_len); + } + char result_name[16]=""; + sprintf(result_name,"FTP_RESULT_%d", g_result_count); + commit_test_result_json(fctx->json_root, result_name); + g_result_count+=1; + free(session_ctx); +} + +extern "C" void FTP_TEST_PLUG_DATA_ENTRY(struct session *sess, int topic_id, const void *msg, void *per_session_ctx, void *plugin_env) +{ + struct ftp_test_context *fctx = (struct ftp_test_context *)per_session_ctx; + struct ftp_message *fmsg = (struct ftp_message *)msg; + enum session_state sstate = session_get_current_state(sess); + if(SESSION_STATE_OPENING == sstate){ + cJSON_AddStringToObject(fctx->json_root, "FTP_LINK_TYPE", "DATA"); + cJSON_AddStringToObject(fctx->json_root, "TUPLE4", session_get0_readable_addr(sess)); + } + enum ftp_msg_type fmsg_type; + struct iovec fmsg_value; + while(ftp_message_iterate(fmsg, &fmsg_type, &fmsg_value)){ + if(fmsg_type == FTP_INVENTORY || fmsg_type == FTP_FILE_CONTENT){ + fctx->tot_file_len += fmsg_value.iov_len; + }else{ + // printf("### ftp data plug: len=%d, %.*s\n", (int)fmsg_value.iov_len, (int)fmsg_value.iov_len, (char *)fmsg_value.iov_base); + ftp_iovec_add_to_json(fctx->json_root, ftp_message_type_to_string(fmsg_type), &fmsg_value, ++fctx->cmd_index); + } + } +} +extern "C" void FTP_TEST_PLUG_CTRL_ENTRY(struct session *sess, int topic_id, const void *msg, void *per_session_ctx, void *plugin_env) +{ + struct ftp_test_context *fctx = (struct ftp_test_context *)per_session_ctx; + struct ftp_message *fmsg = (struct ftp_message *)msg; + enum session_state sstate = session_get_current_state(sess); + if(SESSION_STATE_OPENING == sstate){ + cJSON_AddStringToObject(fctx->json_root, "FTP_LINK_TYPE", "CTRL"); + cJSON_AddStringToObject(fctx->json_root, "TUPLE4", session_get0_readable_addr(sess)); + } + enum ftp_msg_type fmsg_type; + struct iovec fmsg_value; + while(ftp_message_iterate(fmsg, &fmsg_type, &fmsg_value)){ + // printf("### ftp ctrl plug: len=%d, %.*s\n", (int)fmsg_value.iov_len, (int)fmsg_value.iov_len, (char *)fmsg_value.iov_base); + ftp_iovec_add_to_json(fctx->json_root, ftp_message_type_to_string(fmsg_type), &fmsg_value, ++fctx->cmd_index); + } +} + +extern "C" void *FTP_TEST_PLUG_INIT(struct stellar *st) +{ + void *fake_ftp_test_plugin_env = (void *)"_fake_plugin_env_"; + int ftp_test_plug_id = stellar_session_plugin_register(st, ftp_test_plug_session_ctx_new_cb, ftp_test_session_ctx_free_cb, fake_ftp_test_plugin_env); + int ftp_ctrl_topic_id = stellar_session_mq_get_topic_id(st, FTP_DECODER_CTRL_TOPIC); + assert(ftp_ctrl_topic_id >= 0); + int ftp_data_topic_id = stellar_session_mq_get_topic_id(st, FTP_DECODER_DATA_TOPIC); + assert(ftp_data_topic_id >= 0); + + stellar_session_mq_subscribe(st, ftp_ctrl_topic_id, FTP_TEST_PLUG_CTRL_ENTRY, ftp_test_plug_id); + stellar_session_mq_subscribe(st, ftp_data_topic_id, FTP_TEST_PLUG_DATA_ENTRY, ftp_test_plug_id); + + return fake_ftp_test_plugin_env; +} + +extern "C" void FTP_TEST_PLUG_DESTROY(void *plugin_env) +{ + //do nothing + return ; +}
\ No newline at end of file diff --git a/test/ftp_pcap/01-ftp-port-upload-download.pcap b/test/ftp_pcap/01-ftp-port-upload-download.pcap Binary files differnew file mode 100644 index 0000000..314fa6f --- /dev/null +++ b/test/ftp_pcap/01-ftp-port-upload-download.pcap diff --git a/test/ftp_pcap/01-ftp-port-upload-download_C2S.pcap b/test/ftp_pcap/01-ftp-port-upload-download_C2S.pcap Binary files differnew file mode 100644 index 0000000..d8f9b06 --- /dev/null +++ b/test/ftp_pcap/01-ftp-port-upload-download_C2S.pcap diff --git a/test/ftp_pcap/01-ftp-port-upload-download_S2C.pcap b/test/ftp_pcap/01-ftp-port-upload-download_S2C.pcap Binary files differnew file mode 100644 index 0000000..c0e9cc7 --- /dev/null +++ b/test/ftp_pcap/01-ftp-port-upload-download_S2C.pcap diff --git a/test/ftp_pcap/02-ftp_v6_1.pcap b/test/ftp_pcap/02-ftp_v6_1.pcap Binary files differnew file mode 100644 index 0000000..9b7a924 --- /dev/null +++ b/test/ftp_pcap/02-ftp_v6_1.pcap diff --git a/test/ftp_pcap/03-ipv6_eport_upload_download.pcap b/test/ftp_pcap/03-ipv6_eport_upload_download.pcap Binary files differnew file mode 100644 index 0000000..167507a --- /dev/null +++ b/test/ftp_pcap/03-ipv6_eport_upload_download.pcap diff --git a/test/ftp_pcap/04-ftp-banner-no-ftp-characters.pcap b/test/ftp_pcap/04-ftp-banner-no-ftp-characters.pcap Binary files differnew file mode 100644 index 0000000..f646ff2 --- /dev/null +++ b/test/ftp_pcap/04-ftp-banner-no-ftp-characters.pcap diff --git a/test/ftp_pcap/05-only-ctrl-link.pcap b/test/ftp_pcap/05-only-ctrl-link.pcap Binary files differnew file mode 100644 index 0000000..8a5da68 --- /dev/null +++ b/test/ftp_pcap/05-only-ctrl-link.pcap diff --git a/test/ftp_pcap/06-ftp_pasv-upload-download.pcap b/test/ftp_pcap/06-ftp_pasv-upload-download.pcap Binary files differnew file mode 100644 index 0000000..1a391ae --- /dev/null +++ b/test/ftp_pcap/06-ftp_pasv-upload-download.pcap diff --git a/test/ftp_pcap/06-ftp_pasv-upload-download_C2S.pcap b/test/ftp_pcap/06-ftp_pasv-upload-download_C2S.pcap Binary files differnew file mode 100644 index 0000000..753f328 --- /dev/null +++ b/test/ftp_pcap/06-ftp_pasv-upload-download_C2S.pcap diff --git a/test/ftp_pcap/06-ftp_pasv-upload-download_S2C.pcap b/test/ftp_pcap/06-ftp_pasv-upload-download_S2C.pcap Binary files differnew file mode 100644 index 0000000..5ccd508 --- /dev/null +++ b/test/ftp_pcap/06-ftp_pasv-upload-download_S2C.pcap diff --git a/test/ftp_pcap/10-ftp-sdedu.pcap b/test/ftp_pcap/10-ftp-sdedu.pcap Binary files differnew file mode 100644 index 0000000..cf53ce9 --- /dev/null +++ b/test/ftp_pcap/10-ftp-sdedu.pcap diff --git a/test/gtest_ftp_decoder.cpp b/test/gtest_ftp_decoder.cpp new file mode 100644 index 0000000..4ea20e3 --- /dev/null +++ b/test/gtest_ftp_decoder.cpp @@ -0,0 +1,251 @@ +#include <gtest/gtest.h> +#include <unistd.h> +#include <getopt.h> +#include <arpa/inet.h> +#include "ftp_decoder.h" +#include "ftp_decoder_inner.h" +#include "ftp_decoder_util.h" +#ifdef __cplusplus +extern "C" +{ +#endif +#include <stellar/session.h> +#include <stellar/session_exdata.h> +#include <stellar/session_mq.h> +#include <stellar/stellar.h> +#ifdef __cplusplus +} +#endif +/********************* Stellar Mock ********************/ +int stellar_get_worker_thread_num(struct stellar *st) { return 1; } +int stellar_get_current_thread_id(struct stellar *st) { return 0; } +int session_get_current_thread_id(struct session *sess) { return 0; } +const char *session_get0_readable_addr(struct session *sess) { return "TODO"; } +struct session_addr *session_get0_addr(struct session *sess, enum session_addr_type *addr_type) +{ + *addr_type = SESSION_ADDR_TYPE_UNKNOWN; + return NULL; +} +int session_mq_publish_message(struct session *sess, int topic_id, void *msg) { return 0; } +const char *session_get0_current_payload(struct session *sess, size_t *payload_len) +{ + *payload_len = 0; + return "TODO"; +} +void stellar_session_plugin_dettach_current_session(struct session *sess) { return; } +enum session_state session_get_current_state(struct session *sess) { return SESSION_STATE_INVALID; } +int stellar_session_mq_create_topic(struct stellar *st, const char *topic_name, session_msg_free_cb_func *msg_free_cb, void *msg_free_arg) { return 0; } +int stellar_session_mq_subscribe(struct stellar *st, int topic_id, on_session_msg_cb_func *plugin_on_msg_cb, int plugin_id) { return 0; } +int stellar_session_plugin_register(struct stellar *st, session_ctx_new_func session_ctx_new, session_ctx_free_func session_ctx_free, void *plugin_env) { return 0; } +int stellar_session_mq_get_topic_id(struct stellar *st, const char *topic_name) { return 0; } +const struct packet *session_get0_current_packet(struct session *sess) { return NULL; } +int packet_get_direction(const struct packet *pkt) { return PACKET_DIRECTION_UNKNOWN; } +/********************* Stellar Mock End ********************/ + +TEST(ftp_decoder, identify_by_payload) +{ + // true + ASSERT_TRUE(ftp_ctrl_identify_by_payload("USER", 4, PACKET_DIRECTION_C2S)); + ASSERT_TRUE(ftp_ctrl_identify_by_payload("USER test", 9, PACKET_DIRECTION_C2S)); + ASSERT_TRUE(ftp_ctrl_identify_by_payload("220 (vsFTPd 3.0.1)", strlen("220 (vsFTPd 3.0.1)"), PACKET_DIRECTION_S2C)); + ASSERT_TRUE(ftp_ctrl_identify_by_payload("220 (vsFTPD 3.0.2)", strlen("220 (vsFTPD 3.0.2)"), PACKET_DIRECTION_S2C)); + ASSERT_TRUE(ftp_ctrl_identify_by_payload("220 (ftpd 3.0.3)", strlen("220 (ftpd 3.0.3)"), PACKET_DIRECTION_S2C)); + ASSERT_TRUE(ftp_ctrl_identify_by_payload("220-Microsoft FTP Service", strlen("220-Microsoft FTP Service"), PACKET_DIRECTION_S2C)); + + // false + ASSERT_FALSE(ftp_ctrl_identify_by_payload("USR", 3, PACKET_DIRECTION_C2S)); + ASSERT_FALSE(ftp_ctrl_identify_by_payload("USRxx", 5, PACKET_DIRECTION_C2S)); + ASSERT_FALSE(ftp_ctrl_identify_by_payload("PASS", 4, PACKET_DIRECTION_C2S)); + ASSERT_FALSE(ftp_ctrl_identify_by_payload("200 OK", 6, PACKET_DIRECTION_S2C)); + ASSERT_FALSE(ftp_ctrl_identify_by_payload("220 (xxx 3.0.3)", strlen("220 (xxx 3.0.3)"), PACKET_DIRECTION_S2C)); +} + +TEST(ftp_decoder, ftp_cmd_readline) +{ + struct ftp_interact_line line; + ftp_cmd_readline(&line, "USER test", strlen("USER test")); + ASSERT_EQ(line.cmd_refer.iov_len, 4); + ASSERT_EQ(line.arg_refer.iov_len, 4); + ASSERT_EQ(0, strncmp("USER", (char *)line.cmd_refer.iov_base, 4)); + ASSERT_EQ(0, strncmp("test", (char *)line.arg_refer.iov_base, 4)); + + ftp_cmd_readline(&line, "CUSTOM xx1 xx2 xx3", strlen("CUSTOM xx1 xx2 xx3")); + ASSERT_EQ(line.cmd_refer.iov_len, 6); + ASSERT_EQ(line.arg_refer.iov_len, 11); + ASSERT_EQ(0, strncmp("CUSTOM", (char *)line.cmd_refer.iov_base, 6)); + ASSERT_EQ(0, strncmp("xx1 xx2 xx3", (char *)line.arg_refer.iov_base, 11)); +} + +TEST(ftp_decoder, ftp_skip_tail_crlf) +{ + ASSERT_EQ(ftp_skip_tail_crlf("", 0), 0); + ASSERT_EQ(ftp_skip_tail_crlf("\r", 1), 0); + ASSERT_EQ(ftp_skip_tail_crlf("\n", 1), 0); + ASSERT_EQ(ftp_skip_tail_crlf("\r\n", 2), 0); + ASSERT_EQ(ftp_skip_tail_crlf("a\r\n", 3), 1); + ASSERT_EQ(ftp_skip_tail_crlf("abcdefg\r\n", 9), 7); +} + +TEST(ftp_decoder, ipv4_port_style) +{ + int ret; + uint32_t ipv4_net; + uint16_t port_net; + fstring ip_port_style; + // normal + ip_port_style.iov_base = (void *)"192,168,38,2,202,95"; + ip_port_style.iov_len = strlen("192,168,38,2,202,95"); + ret = ftp_parse_ipv4_port_style(&ip_port_style, &ipv4_net, &port_net); + ASSERT_EQ(ret, 0); + ASSERT_EQ(ipv4_net, htonl(0xC0A82602)); + ASSERT_EQ(port_net, htons(51807)); + + // add tab and blank + ip_port_style.iov_base = (void *)" 192,168,38,2,202,95 "; + ip_port_style.iov_len = strlen(" 192,168,38,2,202,95 "); + ret = ftp_parse_ipv4_port_style(&ip_port_style, &ipv4_net, &port_net); + ASSERT_EQ(ret, 0); + ASSERT_EQ(ipv4_net, htonl(0xC0A82602)); + ASSERT_EQ(port_net, htons(51807)); + + // add bracket + ip_port_style.iov_base = (void *)"(192,168,38,2,202,95)"; + ip_port_style.iov_len = strlen("(192,168,38,2,202,95)"); + ret = ftp_parse_ipv4_port_style(&ip_port_style, &ipv4_net, &port_net); + ASSERT_EQ(ret, 0); + ASSERT_EQ(ipv4_net, htonl(0xC0A82602)); + ASSERT_EQ(port_net, htons(51807)); + + // PASV response + ip_port_style.iov_base = (void *)"Entering Passive Mode(192,168,38,2,202,95)"; + ip_port_style.iov_len = strlen("Entering Passive Mode(192,168,38,2,202,95)"); + ret = ftp_parse_ipv4_port_style(&ip_port_style, &ipv4_net, &port_net); + ASSERT_EQ(ret, 0); + ASSERT_EQ(ipv4_net, htonl(0xC0A82602)); + ASSERT_EQ(port_net, htons(51807)); + + // PASV response with blank + ip_port_style.iov_base = (void *)"Entering Passive Mode ( 192,168,38,2,202,95 )"; + ip_port_style.iov_len = strlen("Entering Passive Mode ( 192,168,38,2,202,95 )"); + ret = ftp_parse_ipv4_port_style(&ip_port_style, &ipv4_net, &port_net); + ASSERT_EQ(ret, 0); + ASSERT_EQ(ipv4_net, htonl(0xC0A82602)); + ASSERT_EQ(port_net, htons(51807)); +} + +TEST(ftp_decoder, ipv6_port_style) +{ + int ret; + uint16_t port_net; + fstring ip_port_style; + // normal + ip_port_style.iov_base = (void *)"Entering Extended Passive Mode (|||12345|)"; + ip_port_style.iov_len = strlen("Entering Extended Passive Mode (|||12345|)"); + ret = ftp_parse_ipv6_port_style(&ip_port_style, &port_net); + ASSERT_EQ(ret, 0); + ASSERT_EQ(port_net, htons(12345)); + + // add blank + ip_port_style.iov_base = (void *)"Entering Extended Passive Mode ( |||12345| )"; + ip_port_style.iov_len = strlen("Entering Extended Passive Mode ( |||12345| )"); + ret = ftp_parse_ipv6_port_style(&ip_port_style, &port_net); + ASSERT_EQ(ret, 0); + ASSERT_EQ(port_net, htons(12345)); +} + +TEST(ftp_decoder, ipv6_eprt_style) +{ + int ret; + uint16_t port_net = 0; + fstring eprt_style; + struct in6_addr ipv6addr = {}; + // normal + eprt_style.iov_base = (void *)"|2|1234::abcd|12345|"; + eprt_style.iov_len = strlen("|2|1234::abcd|12345|"); + ret = ftp_parse_eprt_ipport_style(&eprt_style, &ipv6addr, &port_net); + ASSERT_EQ(ret, 0); + struct in6_addr tmpaddr; + inet_pton(AF_INET6, "1234::abcd", &tmpaddr); + ASSERT_EQ(0, memcmp(&tmpaddr, &ipv6addr, sizeof(struct in6_addr))); + ASSERT_EQ(port_net, htons(12345)); + + // loopback address + eprt_style.iov_base = (void *)"|2|::1|12345|"; + eprt_style.iov_len = strlen("|2|::1|12345|"); + ret = ftp_parse_eprt_ipport_style(&eprt_style, &ipv6addr, &port_net); + ASSERT_EQ(ret, 0); + inet_pton(AF_INET6, "::1", &tmpaddr); + ASSERT_EQ(0, memcmp(&tmpaddr, &ipv6addr, sizeof(struct in6_addr))); + ASSERT_EQ(port_net, htons(12345)); + + // add blank + eprt_style.iov_base = (void *)" |2|1234:4321::abcd|12345| "; + eprt_style.iov_len = strlen(" |2|1234:4321::abcd|12345| "); + ret = ftp_parse_eprt_ipport_style(&eprt_style, &ipv6addr, &port_net); + ASSERT_EQ(ret, 0); + inet_pton(AF_INET6, "1234:4321::abcd", &tmpaddr); + ASSERT_EQ(0, memcmp(&tmpaddr, &ipv6addr, sizeof(struct in6_addr))); + ASSERT_EQ(port_net, htons(12345)); +} + +static const char *gtest_cla_short_options = "hLf:"; +static const struct option gtest_cla_long_options[] = + { + {"help", no_argument, NULL, 'h'}, + {"gtest_list_tests", no_argument, NULL, 'L'}, + {"gtest_filter", required_argument, NULL, 'f'}, + {NULL, 0, NULL, 0}}; + +static void usage(int argc, char *argv[]) +{ + printf("args example:\n"); + printf("\t%s short-arg long-arg\n", argv[0]); + printf("\t%s -v \t--version\n", argv[0]); + printf("\t%s -L \t--gtest_list_tests\n", argv[0]); + printf("\t%s -f \t--gtest_filter=ftp_decoder*\n", argv[0]); + exit(0); +} + +int main(int argc, char **argv) +{ + int c; + int to_gtest_argc = 1; + char *to_gtest_args[4] = {(char *)"ftp_gtest", NULL, NULL, NULL}; + char temp_string[1024] = {}; + while (1) + { + c = getopt_long(argc, argv, gtest_cla_short_options, gtest_cla_long_options, NULL); + if (c == -1) + { + break; + } + + switch (c) + { + + case 'h': + usage(argc, argv); + break; + + case 'L': + to_gtest_args[1] = (char *)"--gtest_list_tests"; + to_gtest_argc++; + break; + + case 'f': + strncpy(temp_string, "--gtest_filter=", strlen("--gtest_filter=") + 1); + strcat(temp_string, optarg); + to_gtest_argc++; + to_gtest_args[1] = temp_string; + break; + + case '?': /* invalid or unknown option */ + return -1; + break; + } + } + + testing::InitGoogleTest(&to_gtest_argc, to_gtest_args); + int ret = RUN_ALL_TESTS(); + return ret; +} diff --git a/test/test_result_json/01-ftp-port-upload-download.json b/test/test_result_json/01-ftp-port-upload-download.json new file mode 100644 index 0000000..522beb5 --- /dev/null +++ b/test/test_result_json/01-ftp-port-upload-download.json @@ -0,0 +1,66 @@ +[ + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "192.168.40.139.20>192.168.38.2.51808", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_URI_4": "ftp://192.168.40.139/2001.03147v1.pdf", + "FTP_TRANS_MODE_5": "PORT", + "FTP_TRANS_DIR_6": "STOR", + "total-payload-len": 681116, + "name": "FTP_RESULT_0" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "192.168.40.139.20>192.168.38.2.51809", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "PORT", + "FTP_TRANS_DIR_5": "LIST", + "total-payload-len": 76, + "name": "FTP_RESULT_1" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "192.168.40.139.20>192.168.38.2.51810", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_URI_4": "ftp://192.168.40.139/3cdaemon-v2r10.zip", + "FTP_TRANS_MODE_5": "PORT", + "FTP_TRANS_DIR_6": "RETR", + "total-payload-len": 953457, + "name": "FTP_RESULT_2" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "192.168.40.139.20>192.168.38.2.51811", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "PORT", + "FTP_TRANS_DIR_5": "LIST", + "total-payload-len": 76, + "name": "FTP_RESULT_3" + }, + { + "FTP_LINK_TYPE": "CTRL", + "TUPLE4": "192.168.38.2.51805>192.168.40.139.21", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "PORT", + "FTP_TRANS_DIR_5": "LIST", + "FTP_TRANS_MODE_6": "PORT", + "FTP_URI_7": "ftp://192.168.40.139/2001.03147v1.pdf", + "FTP_TRANS_DIR_8": "STOR", + "FTP_TRANS_MODE_9": "PORT", + "FTP_TRANS_MODE_10": "PORT", + "FTP_URI_11": "ftp://192.168.40.139/3cdaemon-v2r10.zip", + "FTP_TRANS_DIR_12": "RETR", + "FTP_TRANS_MODE_13": "PORT", + "name": "FTP_RESULT_4" + } +]
\ No newline at end of file diff --git a/test/test_result_json/01-ftp-port-upload-download_C2S.json b/test/test_result_json/01-ftp-port-upload-download_C2S.json new file mode 100644 index 0000000..d04e6a9 --- /dev/null +++ b/test/test_result_json/01-ftp-port-upload-download_C2S.json @@ -0,0 +1,28 @@ +[ + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "192.168.40.139.20>192.168.38.2.51808", + "FTP_ACCOUNT_1": "ftp", + "FTP_PASSWORD_2": "111111", + "FTP_URI_3": "ftp://192.168.40.139/2001.03147v1.pdf", + "FTP_TRANS_MODE_4": "PORT", + "FTP_TRANS_DIR_5": "STOR", + "total-payload-len": 681116, + "name": "FTP_RESULT_0" + }, + { + "FTP_ACCOUNT_1": "ftp", + "FTP_PASSWORD_2": "111111", + "FTP_TRANS_MODE_3": "PORT", + "FTP_TRANS_DIR_4": "LIST", + "FTP_TRANS_MODE_5": "PORT", + "FTP_URI_6": "ftp://192.168.40.139/2001.03147v1.pdf", + "FTP_TRANS_DIR_7": "STOR", + "FTP_TRANS_MODE_8": "PORT", + "FTP_TRANS_MODE_9": "PORT", + "FTP_URI_10": "ftp://192.168.40.139/3cdaemon-v2r10.zip", + "FTP_TRANS_DIR_11": "RETR", + "FTP_TRANS_MODE_12": "PORT", + "name": "FTP_RESULT_1" + } +]
\ No newline at end of file diff --git a/test/test_result_json/01-ftp-port-upload-download_S2C.json b/test/test_result_json/01-ftp-port-upload-download_S2C.json new file mode 100644 index 0000000..346c6dc --- /dev/null +++ b/test/test_result_json/01-ftp-port-upload-download_S2C.json @@ -0,0 +1,8 @@ +[ + { + "FTP_LINK_TYPE": "CTRL", + "TUPLE4": "192.168.38.2.51805>192.168.40.139.21", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "name": "FTP_RESULT_0" + } +]
\ No newline at end of file diff --git a/test/test_result_json/02-ftp_v6_1.json b/test/test_result_json/02-ftp_v6_1.json new file mode 100644 index 0000000..1a6e236 --- /dev/null +++ b/test/test_result_json/02-ftp_v6_1.json @@ -0,0 +1,57 @@ +[ + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "::1.39002>::1.10791", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_DIR_4": "LIST", + "total-payload-len": 65, + "name": "FTP_RESULT_0" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "::1.41426>::1.20987", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_URI_4": "ftp://::1/123.txt", + "FTP_TRANS_DIR_5": "RETR", + "total-payload-len": 44, + "name": "FTP_RESULT_1" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "::1.59846>::1.50928", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_URI_4": "ftp://::1/test.txt", + "FTP_TRANS_DIR_5": "STOR", + "total-payload-len": 140, + "name": "FTP_RESULT_2" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "::1.50872>::1.14779", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_DIR_4": "LIST", + "total-payload-len": 131, + "name": "FTP_RESULT_3" + }, + { + "FTP_LINK_TYPE": "CTRL", + "TUPLE4": "::1.47756>::1.21", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_DIR_4": "LIST", + "FTP_URI_5": "ftp://::1/123.txt", + "FTP_TRANS_DIR_6": "RETR", + "FTP_URI_7": "ftp://::1/test.txt", + "FTP_TRANS_DIR_8": "STOR", + "name": "FTP_RESULT_4" + } +]
\ No newline at end of file diff --git a/test/test_result_json/03-ipv6_eport_upload_download.json b/test/test_result_json/03-ipv6_eport_upload_download.json new file mode 100644 index 0000000..557b942 --- /dev/null +++ b/test/test_result_json/03-ipv6_eport_upload_download.json @@ -0,0 +1,77 @@ +[ + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "::1.20>::1.49998", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "EPRT", + "FTP_TRANS_DIR_5": "LIST", + "total-payload-len": 65, + "name": "FTP_RESULT_0" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "::1.20>::1.40784", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_URI_4": "ftp://::1/123.txt", + "FTP_TRANS_MODE_5": "EPRT", + "FTP_TRANS_DIR_6": "RETR", + "total-payload-len": 44, + "name": "FTP_RESULT_1" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "::1.20>::1.32865", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "EPRT", + "FTP_TRANS_DIR_5": "LIST", + "total-payload-len": 65, + "name": "FTP_RESULT_2" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "::1.20>::1.55267", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_URI_4": "ftp://::1/test.txt", + "FTP_TRANS_MODE_5": "EPRT", + "FTP_TRANS_DIR_6": "STOR", + "total-payload-len": 140, + "name": "FTP_RESULT_3" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "::1.20>::1.34844", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "EPRT", + "FTP_TRANS_DIR_5": "LIST", + "total-payload-len": 131, + "name": "FTP_RESULT_4" + }, + { + "FTP_LINK_TYPE": "CTRL", + "TUPLE4": "::1.49594>::1.21", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "EPRT", + "FTP_TRANS_DIR_5": "LIST", + "FTP_TRANS_MODE_6": "EPRT", + "FTP_URI_7": "ftp://::1/123.txt", + "FTP_TRANS_DIR_8": "RETR", + "FTP_TRANS_MODE_9": "EPRT", + "FTP_TRANS_MODE_10": "EPRT", + "FTP_URI_11": "ftp://::1/test.txt", + "FTP_TRANS_DIR_12": "STOR", + "FTP_TRANS_MODE_13": "EPRT", + "name": "FTP_RESULT_5" + } +]
\ No newline at end of file diff --git a/test/test_result_json/04-ftp-banner-no-ftp-characters.json b/test/test_result_json/04-ftp-banner-no-ftp-characters.json new file mode 100644 index 0000000..ddb097b --- /dev/null +++ b/test/test_result_json/04-ftp-banner-no-ftp-characters.json @@ -0,0 +1,26 @@ +[ + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "10.20.144.150.35976>10.20.144.151.16013", + "FTP_BANNER_1": "Connection will close if idle more than 5 minutes.", + "FTP_ACCOUNT_2": "cdts3500", + "FTP_PASSWORD_3": "cdts3500", + "FTP_URI_4": "ftp://10.20.144.151/qgpl/apkeyf.apkeyf", + "FTP_TRANS_MODE_5": "PASV", + "FTP_TRANS_DIR_6": "RETR", + "total-payload-len": 439, + "name": "FTP_RESULT_0" + }, + { + "FTP_LINK_TYPE": "CTRL", + "TUPLE4": "10.20.144.150.35974>10.20.144.151.21", + "FTP_BANNER_1": "at fran.csg.stercomm.com.", + "FTP_BANNER_2": "Connection will close if idle more than 5 minutes.", + "FTP_ACCOUNT_3": "cdts3500", + "FTP_PASSWORD_4": "cdts3500", + "FTP_TRANS_MODE_5": "PASV", + "FTP_URI_6": "ftp://10.20.144.151/qgpl/apkeyf.apkeyf", + "FTP_TRANS_DIR_7": "RETR", + "name": "FTP_RESULT_1" + } +]
\ No newline at end of file diff --git a/test/test_result_json/05-only-ctrl-link.json b/test/test_result_json/05-only-ctrl-link.json new file mode 100644 index 0000000..a4a5068 --- /dev/null +++ b/test/test_result_json/05-only-ctrl-link.json @@ -0,0 +1,11 @@ +[{ + "FTP_LINK_TYPE": "CTRL", + "TUPLE4": "192.168.20.1.40920>192.168.60.1.21", + "FTP_BANNER_1": "(vsFTPd 3.0.2)", + "FTP_ACCOUNT_2": "test1", + "FTP_PASSWORD_3": "iiecas2021", + "FTP_TRANS_MODE_4": "PASV", + "FTP_URI_5": "ftp://192.168.60.1//home/test1/20210622201359人肉炸弹.txt", + "FTP_TRANS_DIR_6": "STOR", + "name": "FTP_RESULT_0" +}]
\ No newline at end of file diff --git a/test/test_result_json/06-ftp_pasv-upload-download.json b/test/test_result_json/06-ftp_pasv-upload-download.json new file mode 100644 index 0000000..7ba734d --- /dev/null +++ b/test/test_result_json/06-ftp_pasv-upload-download.json @@ -0,0 +1,65 @@ +[ + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "127.0.0.1.59814>127.0.0.1.13144", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "PASV", + "FTP_TRANS_DIR_5": "LIST", + "total-payload-len": 61, + "name": "FTP_RESULT_0" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "127.0.0.1.38754>127.0.0.1.15810", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "PASV", + "FTP_TRANS_DIR_5": "LIST", + "total-payload-len": 131, + "name": "FTP_RESULT_1" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "127.0.0.1.52584>127.0.0.1.6554", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_URI_4": "ftp://127.0.0.1/123.txt", + "FTP_TRANS_MODE_5": "PASV", + "FTP_TRANS_DIR_6": "RETR", + "total-payload-len": 44, + "name": "FTP_RESULT_2" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "127.0.0.1.32799>127.0.0.1.58439", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_URI_4": "ftp://127.0.0.1/test.txt", + "FTP_TRANS_MODE_5": "PASV", + "FTP_TRANS_DIR_6": "STOR", + "total-payload-len": 140, + "name": "FTP_RESULT_3" + }, + { + "FTP_LINK_TYPE": "CTRL", + "TUPLE4": "127.0.0.1.42042>127.0.0.1.21", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "FTP_ACCOUNT_2": "ftp", + "FTP_PASSWORD_3": "111111", + "FTP_TRANS_MODE_4": "PASV", + "FTP_TRANS_DIR_5": "LIST", + "FTP_TRANS_MODE_6": "PASV", + "FTP_TRANS_MODE_7": "PASV", + "FTP_URI_8": "ftp://127.0.0.1/123.txt", + "FTP_TRANS_DIR_9": "RETR", + "FTP_TRANS_MODE_10": "PASV", + "FTP_URI_11": "ftp://127.0.0.1/test.txt", + "FTP_TRANS_DIR_12": "STOR", + "name": "FTP_RESULT_4" + } +]
\ No newline at end of file diff --git a/test/test_result_json/06-ftp_pasv-upload-download_C2S.json b/test/test_result_json/06-ftp_pasv-upload-download_C2S.json new file mode 100644 index 0000000..30d28c7 --- /dev/null +++ b/test/test_result_json/06-ftp_pasv-upload-download_C2S.json @@ -0,0 +1,18 @@ +[ + { + "FTP_LINK_TYPE": "CTRL", + "TUPLE4": "127.0.0.1.42042>127.0.0.1.21", + "FTP_ACCOUNT_1": "ftp", + "FTP_PASSWORD_2": "111111", + "FTP_TRANS_MODE_3": "PASV", + "FTP_TRANS_DIR_4": "LIST", + "FTP_TRANS_MODE_5": "PASV", + "FTP_TRANS_MODE_6": "PASV", + "FTP_URI_7": "ftp://127.0.0.1/123.txt", + "FTP_TRANS_DIR_8": "RETR", + "FTP_TRANS_MODE_9": "PASV", + "FTP_URI_10": "ftp://127.0.0.1/test.txt", + "FTP_TRANS_DIR_11": "STOR", + "name": "FTP_RESULT_0" + } +]
\ No newline at end of file diff --git a/test/test_result_json/06-ftp_pasv-upload-download_S2C.json b/test/test_result_json/06-ftp_pasv-upload-download_S2C.json new file mode 100644 index 0000000..86d0af6 --- /dev/null +++ b/test/test_result_json/06-ftp_pasv-upload-download_S2C.json @@ -0,0 +1,29 @@ +[ + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "127.0.0.1.59814>127.0.0.1.13144", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "total-payload-len": 61, + "name": "FTP_RESULT_0" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "127.0.0.1.38754>127.0.0.1.15810", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "total-payload-len": 131, + "name": "FTP_RESULT_1" + }, + { + "FTP_LINK_TYPE": "DATA", + "TUPLE4": "127.0.0.1.52584>127.0.0.1.6554", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "total-payload-len": 44, + "name": "FTP_RESULT_2" + }, + { + "FTP_LINK_TYPE": "CTRL", + "TUPLE4": "127.0.0.1.42042>127.0.0.1.21", + "FTP_BANNER_1": "Welcome to blah FTP service.", + "name": "FTP_RESULT_3" + } +]
\ No newline at end of file diff --git a/test/test_result_json/empty.json b/test/test_result_json/empty.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/test/test_result_json/empty.json @@ -0,0 +1 @@ +[] diff --git a/test/uthash-2.3.0/include/utarray.h b/test/uthash-2.3.0/include/utarray.h new file mode 100644 index 0000000..992fe7c --- /dev/null +++ b/test/uthash-2.3.0/include/utarray.h @@ -0,0 +1,248 @@ +/* +Copyright (c) 2008-2021, Troy D. Hanson http://troydhanson.github.com/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/test/uthash-2.3.0/include/uthash.h b/test/uthash-2.3.0/include/uthash.h new file mode 100644 index 0000000..ac78fda --- /dev/null +++ b/test/uthash-2.3.0/include/uthash.h @@ -0,0 +1,1136 @@ +/* +Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.com/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 */ +#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/test/uthash-2.3.0/include/utlist.h b/test/uthash-2.3.0/include/utlist.h new file mode 100644 index 0000000..6230a67 --- /dev/null +++ b/test/uthash-2.3.0/include/utlist.h @@ -0,0 +1,1073 @@ +/* +Copyright (c) 2007-2021, Troy D. Hanson http://troydhanson.github.com/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/test/uthash-2.3.0/include/utringbuffer.h b/test/uthash-2.3.0/include/utringbuffer.h new file mode 100644 index 0000000..cf48131 --- /dev/null +++ b/test/uthash-2.3.0/include/utringbuffer.h @@ -0,0 +1,108 @@ +/* +Copyright (c) 2015-2021, Troy D. Hanson http://troydhanson.github.com/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/test/uthash-2.3.0/include/utstack.h b/test/uthash-2.3.0/include/utstack.h new file mode 100644 index 0000000..2dbfec8 --- /dev/null +++ b/test/uthash-2.3.0/include/utstack.h @@ -0,0 +1,88 @@ +/* +Copyright (c) 2018-2021, Troy D. Hanson http://troydhanson.github.com/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/test/uthash-2.3.0/include/utstring.h b/test/uthash-2.3.0/include/utstring.h new file mode 100644 index 0000000..009b4c8 --- /dev/null +++ b/test/uthash-2.3.0/include/utstring.h @@ -0,0 +1,407 @@ +/* +Copyright (c) 2008-2021, Troy D. Hanson http://troydhanson.github.com/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/vendor/CMakeLists.txt b/vendor/CMakeLists.txt new file mode 100644 index 0000000..c241665 --- /dev/null +++ b/vendor/CMakeLists.txt @@ -0,0 +1,23 @@ +# CMakeFiles for 3rd vendor library + +include(ExternalProject) + +# 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) diff --git a/vendor/googletest-release-1.8.0.tar.gz b/vendor/googletest-release-1.8.0.tar.gz Binary files differnew file mode 100644 index 0000000..a40df33 --- /dev/null +++ b/vendor/googletest-release-1.8.0.tar.gz diff --git a/vendor/sapp-4.3.56.a47b3b5-1.el8.x86_64.rpm b/vendor/sapp-4.3.56.a47b3b5-1.el8.x86_64.rpm Binary files differnew file mode 100644 index 0000000..58e3f92 --- /dev/null +++ b/vendor/sapp-4.3.56.a47b3b5-1.el8.x86_64.rpm diff --git a/vendor/sapp-devel-4.3.56.a47b3b5-1.el8.x86_64.rpm b/vendor/sapp-devel-4.3.56.a47b3b5-1.el8.x86_64.rpm Binary files differnew file mode 100644 index 0000000..9affbf6 --- /dev/null +++ b/vendor/sapp-devel-4.3.56.a47b3b5-1.el8.x86_64.rpm diff --git a/vendor/stellar-on-sapp-2.1.1.7875675-1.el8.x86_64.rpm b/vendor/stellar-on-sapp-2.1.1.7875675-1.el8.x86_64.rpm Binary files differnew file mode 100644 index 0000000..695d6bc --- /dev/null +++ b/vendor/stellar-on-sapp-2.1.1.7875675-1.el8.x86_64.rpm |
