#!/usr/bin/env bash

set -e

LINE_SPACES=${LINE_SPACES:-48}
STRACE_EXEC="${STRACE_EXEC}"
MYDIR="$(realpath "$(dirname ${0})")"
nDPId_test_EXEC="$(realpath "${2:-"${MYDIR}/../nDPId-test"}")"
NETCAT_EXEC="$(which nc) -q 0 -l 127.0.0.1 9000"
JSON_VALIDATOR="$(realpath "${3:-"${MYDIR}/../examples/py-schema-validation/py-schema-validation.py"}")"
SEMN_VALIDATOR="$(realpath "${4:-"${MYDIR}/../examples/py-semantic-validation/py-semantic-validation.py"}")"
FLOW_INFO="$(realpath "${5:-"${MYDIR}/../examples/py-flow-info/flow-info.py"}")"
NDPISRVD_ANALYSED="$(realpath "${6:-"$(dirname ${nDPId_test_EXEC})/nDPIsrvd-analysed"}")"
NDPISRVD_COLLECTD="$(realpath "${6:-"$(dirname ${nDPId_test_EXEC})/nDPIsrvd-collectd"}")"
IS_GIT=$(test -d "${MYDIR}/../.git" -o -f "${MYDIR}/../.git" && printf '1' || printf '0')

function usage()
{
cat <<EOF
usage: ${0} [path-to-nDPI-source-root] \\
    [path-to-nDPId-test-exec] [path-to-nDPId-JSON-validator] [path-to-nDPId-SEMANTIC-validator]

    path-to-nDPId-test-exec defaults to         ${nDPId_test_EXEC}
    path-to-nDPId-JSON-validator defaults to    ${JSON_VALIDATOR}
    path-to-nDPId-SEMANTIC-validator default to ${SEMN_VALIDATOR}
    path-to-nDPId-flow-info defaults to         ${FLOW_INFO}
    path-to-nDPIsrvd-analysed defaults to       ${NDPISRVD_ANALYSED}
    path-to-nDPIsrvd-collectd defaults to       ${NDPISRVD_COLLECTD}
EOF
return 0
}

test -z "$(which flock)" && { printf '%s\n' 'flock not found'; exit 1; }
test -z "$(which pkill)" && { printf '%s\n' 'pkill not found'; exit 1; }
test -z "$(which nc)" && { printf '%s\n' 'nc not found'; exit 1; }
test -z "$(which ss)" && { printf '%s\n' 'ss not found'; exit 1; }
test -z "$(which cat)" && { printf '%s\n' 'cat not found'; exit 1; }
test -z "$(which grep)" && { printf '%s\n' 'grep not found'; exit 1; }

if [ $# -eq 0 -a -x "${MYDIR}/../libnDPI/tests/pcap" ]; then
    nDPI_SOURCE_ROOT="${MYDIR}/../libnDPI"
elif [ $# -ne 1 -a $# -ne 2 -a $# -ne 3 -a $# -ne 4 ]; then
    usage
    exit 2
else
    nDPI_SOURCE_ROOT="$(realpath "${1}")"
fi

if [ ! -x "${nDPI_SOURCE_ROOT}/tests/pcap" ]; then
    printf 'PCAP directory %s does not exist or you do not have the permission to access it.\n' "${nDPI_SOURCE_ROOT}/tests/pcap" >&2
    exit 2
fi

LOCKFILE="$(realpath "${0}").lock"

touch "${LOCKFILE}"
exec 42< "${LOCKFILE}"
$(which flock) -x -n 42 || {
    printf '%s\n' "Could not aquire file lock for ${0}. Already running instance?" >&2;
    exit 3;
}
function sighandler()
{
    printf '%s\n' ' Received shutdown SIGNAL, bye' >&2
    $(which pkill) -P $$
    wait
    rm -f "${LOCKFILE}"
    exit 4
}
trap sighandler SIGINT SIGTERM

if [ ! -x "${nDPId_test_EXEC}" ]; then
    usage
    printf '\n%s\n' "Required nDPId-test executable does not exist; ${nDPId_test_EXEC}"
    exit 5
fi

$(which nc) -h |& head -n1 | grep -qoE '^OpenBSD netcat' || {
    printf '%s\n' "$(which nc): OpenBSD netcat (nc) version required!" >&2;
    printf '%s\n' "$(which nc): Your version: $(nc -h |& head -n1)" >&2;
    exit 6;
}

nDPI_TEST_DIR="$(realpath "${nDPI_SOURCE_ROOT}/tests/pcap")"
cd "${nDPI_TEST_DIR}"

cat <<EOF
nDPId-test: ${nDPId_test_EXEC}
nDPI pcaps: ${nDPI_TEST_DIR} ($(ls -l *.pcap *.pcapng *.cap | wc -l) total)

--------------------------
-- nDPId PCAP diff tests --
--------------------------

EOF

mkdir -p /tmp/nDPId-test-stderr
mkdir -p /tmp/nDPId-test-stdout

set +e
TESTS_FAILED=0

${nDPId_test_EXEC} -h 2>/dev/null
if [ $? -ne 1 ]; then
    printf '%s\n' "nDPId-test: ${nDPId_test_EXEC} seems to be an invalid executable"
    exit 7
fi

for pcap_file in *.pcap *.pcapng *.cap; do
    printf '%s\n' "-- CMD: ${nDPId_test_EXEC} $(realpath "${pcap_file}")" \
        >"/tmp/nDPId-test-stderr/${pcap_file}.out"
    printf '%s\n' "-- OUT: ${MYDIR}/results/${pcap_file}.out" \
        >>"/tmp/nDPId-test-stderr/${pcap_file}.out"

    printf "%-${LINE_SPACES}s\t" "${pcap_file}"

    if [ ! -z "${STRACE_EXEC}" ]; then
        STRACE_CMD="${STRACE_EXEC} -f -e decode-fds=path,socket,dev,pidfd -s 1024 -o /tmp/nDPId-test-stderr/${pcap_file}.strace.out"
    else
        STRACE_CMD=""
    fi
    ${STRACE_CMD} ${nDPId_test_EXEC} "${pcap_file}" \
        >"/tmp/nDPId-test-stdout/${pcap_file}.out.new" \
        2>>"/tmp/nDPId-test-stderr/${pcap_file}.out"
    nDPId_test_RETVAL=$?

    if [ ${nDPId_test_RETVAL} -eq 0 ]; then
        if [ ! -r "${MYDIR}/results/${pcap_file}.out" ]; then
            printf '%s\n' '[NEW]'
            test ${IS_GIT} -eq 1 && \
                mv -v "/tmp/nDPId-test-stdout/${pcap_file}.out.new" \
                      "${MYDIR}/results/${pcap_file}.out"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        elif diff -u0 "${MYDIR}/results/${pcap_file}.out" \
                      "/tmp/nDPId-test-stdout/${pcap_file}.out.new" >/dev/null; then
            printf '%s\n' '[OK]'
            rm -f "/tmp/nDPId-test-stdout/${pcap_file}.out.new"
        else
            printf '%s\n' '[DIFF]'
            diff -u0 "${MYDIR}/results/${pcap_file}.out" \
                     "/tmp/nDPId-test-stdout/${pcap_file}.out.new"
            test ${IS_GIT} -eq 1 && \
                mv -v "/tmp/nDPId-test-stdout/${pcap_file}.out.new" \
                      "${MYDIR}/results/${pcap_file}.out"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        fi
    else
        printf '%s\n' '[FAIL]'
        printf '%s\n' '----------------------------------------'
        printf '%s\n' "-- STDERR of ${pcap_file}: /tmp/nDPId-test-stderr/${pcap_file}.out"
        cat "/tmp/nDPId-test-stderr/${pcap_file}.out"
        TESTS_FAILED=$((TESTS_FAILED + 1))
    fi
done

for out_file in ${MYDIR}/results/*.out; do
    pcap_file="$(basename ${out_file%.out})"
    if [ ! -r "${pcap_file}" ]; then
        printf "%-${LINE_SPACES}s\t%s\n" "${pcap_file}" "[MISSING]"
        TESTS_FAILED=$((TESTS_FAILED + 1))
    fi
done

function validate_results()
{
    prefix_str="${1}"
    pcap_file="$(basename ${2})"
    result_file="${3}"
    validator_exec="${4}"

    printf "%s %-$((${LINE_SPACES} - ${#prefix_str}))s\t" "${prefix_str}" "${pcap_file}"
    printf '%s\n' "-- ${prefix_str}" >>"/tmp/nDPId-test-stderr/${pcap_file}.out"

    if [ ! -r "${result_file}" ]; then
        printf ' %s\n' '[MISSING]'
        return 1
    fi

    # Note that the grep command is required as we generate a summary in the results file.
    cat "${result_file}" | grep -vE '^~~.*$' | ${NETCAT_EXEC} &
    nc_pid=$!
    printf '%s\n' "-- ${validator_exec}" >>"/tmp/nDPId-test-stderr/${pcap_file}.out"
    ${validator_exec} 2>>"/tmp/nDPId-test-stderr/${pcap_file}.out"
    if [ $? -eq 0 ]; then
        printf ' %s\n' '[OK]'
    else
        printf ' %s\n' '[FAIL]'
        printf '%s\n' '----------------------------------------'
        printf '%s\n' "-- STDERR of ${pcap_file}: /tmp/nDPId-test-stderr/${pcap_file}.out"
        cat "/tmp/nDPId-test-stderr/${pcap_file}.out"
        return 1
    fi
    kill -SIGTERM ${nc_pid} 2>/dev/null
    wait ${nc_pid} 2>/dev/null

    return 0
}

cat <<EOF

--------------------
-- Flow Info DIFF --
--------------------

EOF

cd "${MYDIR}"
for out_file in results/*.out; do
    result_file="$(basename ${out_file})"
    printf "%-${LINE_SPACES}s\t" "${result_file}"
    cat "${out_file}" | grep -vE '^~~.*$' | ${NETCAT_EXEC} &
    nc_pid=$!
    ${FLOW_INFO} --host 127.0.0.1 --port 9000 \
        --no-color --no-statusbar --hide-instance-info \
        --print-analyse-results --print-hostname >"/tmp/nDPId-test-stdout/${result_file}.new" 2>>"/tmp/nDPId-test-stderr/${result_file}"
    kill -SIGTERM ${nc_pid} 2>/dev/null
    wait ${nc_pid} 2>/dev/null
    if [ ! -r "${MYDIR}/results/flow-info/${result_file}" ]; then
        printf '%s\n' '[NEW]'
        test ${IS_GIT} -eq 1 && \
            mv -v "/tmp/nDPId-test-stdout/${result_file}.new" \
                  "${MYDIR}/results/flow-info/${result_file}"
        TESTS_FAILED=$((TESTS_FAILED + 1))
    elif diff -u0 "${MYDIR}/results/flow-info/${result_file}" \
                  "/tmp/nDPId-test-stdout/${result_file}.new" >/dev/null; then
        printf '%s\n' '[OK]'
        rm -f "/tmp/nDPId-test-stdout/${result_file}.new"
    else
        printf '%s\n' '[DIFF]'
        diff -u0 "${MYDIR}/results/flow-info/${result_file}" \
                 "/tmp/nDPId-test-stdout/${result_file}.new"
        test ${IS_GIT} -eq 1 && \
            mv -v "/tmp/nDPId-test-stdout/${result_file}.new" \
                  "${MYDIR}/results/flow-info/${result_file}"
        TESTS_FAILED=$((TESTS_FAILED + 1))
    fi
done

for out_file in ${MYDIR}/results/flow-info/*.out; do
    result_file="$(basename ${out_file})"
    if [ ! -r "${MYDIR}/results/${result_file}" ]; then
        printf "%-${LINE_SPACES}s\t%s\n" "${result_file}" "[MISSING]"
        TESTS_FAILED=$((TESTS_FAILED + 1))
    fi
done

cat <<EOF

-----------------------
-- Flow Analyse DIFF --
-----------------------

EOF

if [ -x "${NDPISRVD_ANALYSED}" ]; then
    cd "${MYDIR}"
    for out_file in results/*.out; do
        result_file="$(basename ${out_file})"
        printf "%-${LINE_SPACES}s\t" "${result_file}"
        cat "${out_file}" | grep -vE '^~~.*$' | ${NETCAT_EXEC} &
        nc_pid=$!
        while ! ss -4 -t -n -l | grep -q '127.0.0.1:9000'; do sleep 0.5; printf '%s\n' 'Waiting until socket 127.0.0.1:9000 is available..' >>"/tmp/nDPId-test-stderr/${result_file}"; done
        ${NDPISRVD_ANALYSED} -s '127.0.0.1:9000' -o "/tmp/nDPId-test-stdout/${result_file}.csv.new" 2>>"/tmp/nDPId-test-stderr/${result_file}" 1>&2
        kill -SIGTERM ${nc_pid} 2>/dev/null
        wait ${nc_pid} 2>/dev/null
        if [ ! -r "${MYDIR}/results/flow-analyse/${result_file}" ]; then
            printf '%s\n' '[NEW]'
            test ${IS_GIT} -eq 1 && \
                mv -v "/tmp/nDPId-test-stdout/${result_file}.csv.new" \
                      "${MYDIR}/results/flow-analyse/${result_file}"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        elif diff -u0 "${MYDIR}/results/flow-analyse/${result_file}" \
                      "/tmp/nDPId-test-stdout/${result_file}.csv.new" >/dev/null; then
            printf '%s\n' '[OK]'
            rm -f "/tmp/nDPId-test-stdout/${result_file}.csv.new"
        else
            printf '%s\n' '[DIFF]'
            diff -u0 "${MYDIR}/results/flow-analyse/${result_file}" \
                     "/tmp/nDPId-test-stdout/${result_file}.csv.new"
            test ${IS_GIT} -eq 1 && \
                mv -v "/tmp/nDPId-test-stdout/${result_file}.csv.new" \
                      "${MYDIR}/results/flow-analyse/${result_file}"
            cat "/tmp/nDPId-test-stderr/${result_file}"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        fi
    done

    for out_file in ${MYDIR}/results/flow-analyse/*.out; do
        result_file="$(basename ${out_file})"
        if [ ! -r "${MYDIR}/results/${result_file}" ]; then
            printf "%-${LINE_SPACES}s\t%s\n" "${result_file}" "[MISSING]"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        fi
    done
else
    printf '%s\n' "Not found or not executable: ${NDPISRVD_ANALYSED}"
fi

cat <<EOF

------------------------------
-- Collectd Statistics DIFF --
------------------------------

EOF

if [ -x "${NDPISRVD_COLLECTD}" ]; then
    cd "${MYDIR}"
    for out_file in results/*.out; do
        result_file="$(basename ${out_file})"
        printf "%-${LINE_SPACES}s\t" "${result_file}"
        cat "${out_file}" | grep -vE '^~~.*$' | ${NETCAT_EXEC} &
        nc_pid=$!
        while ! ss -4 -t -n -l | grep -q '127.0.0.1:9000'; do sleep 0.5; printf '%s\n' 'Waiting until socket 127.0.0.1:9000 is available..' >>"/tmp/nDPId-test-stderr/${result_file}"; done
        ${NDPISRVD_COLLECTD} -s '127.0.0.1:9000' 2>>"/tmp/nDPId-test-stderr/${result_file}" 1>"/tmp/nDPId-test-stdout/${result_file}.stats.new"
        kill -SIGTERM ${nc_pid} 2>/dev/null
        wait ${nc_pid} 2>/dev/null
        if [ ! -r "${MYDIR}/results/collectd-stats/${result_file}" ]; then
            printf '%s\n' '[NEW]'
            test ${IS_GIT} -eq 1 && \
                mv -v "/tmp/nDPId-test-stdout/${result_file}.stats.new" \
                      "${MYDIR}/results/collectd-stats/${result_file}"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        elif diff -u0 "${MYDIR}/results/collectd-stats/${result_file}" \
                      "/tmp/nDPId-test-stdout/${result_file}.stats.new" >/dev/null; then
            printf '%s\n' '[OK]'
            rm -f "/tmp/nDPId-test-stdout/${result_file}.stats.new"
        else
            printf '%s\n' '[DIFF]'
            diff -u0 "${MYDIR}/results/collectd-stats/${result_file}" \
                     "/tmp/nDPId-test-stdout/${result_file}.stats.new"
            test ${IS_GIT} -eq 1 && \
                mv -v "/tmp/nDPId-test-stdout/${result_file}.stats.new" \
                      "${MYDIR}/results/collectd-stats/${result_file}"
            cat "/tmp/nDPId-test-stderr/${result_file}"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        fi
    done

    for out_file in ${MYDIR}/results/collectd-stats/*.out; do
        result_file="$(basename ${out_file})"
        if [ ! -r "${MYDIR}/results/${result_file}" ]; then
            printf "%-${LINE_SPACES}s\t%s\n" "${result_file}" "[MISSING]"
            TESTS_FAILED=$((TESTS_FAILED + 1))
        fi
    done
else
    printf '%s\n' "Not found or not executable: ${NDPISRVD_COLLECTD}"
fi

cat <<EOF

--------------------------------
-- SCHEMA/SEMANTIC Validation --
--------------------------------

netcat (OpenBSD) exec + args: ${NETCAT_EXEC}

EOF

cd "${MYDIR}"
for out_file in results/*.out; do
    pcap_file="${nDPI_TEST_DIR}/$(basename ${out_file%.out})"
    if [ ! -r "${pcap_file}" ]; then
        printf "%-${LINE_SPACES}s\t%s\n" "$(basename ${pcap_file})" '[MISSING]'
        TESTS_FAILED=$((TESTS_FAILED + 1))
    else
        validate_results "SCHEMA  " "${pcap_file}" "${out_file}" \
            "${JSON_VALIDATOR} --host 127.0.0.1 --port 9000"
        if [ $? -ne 0 ]; then
            TESTS_FAILED=$((TESTS_FAILED + 1))
            continue
        fi

        validate_results "SEMANTIC" "${pcap_file}" "${out_file}" \
            "${SEMN_VALIDATOR} --host 127.0.0.1 --port 9000 --strict"
        if [ $? -ne 0 ]; then
            TESTS_FAILED=$((TESTS_FAILED + 1))
            continue
        fi
    fi
done

cat <<EOF

Done. For more information see text files in:
    /tmp/nDPId-test-stdout/
    /tmp/nDPId-test-stderr/

EOF

if [ ${TESTS_FAILED} -eq 0 ]; then
cat <<EOF

--------------------------
-- All tests succeeded. --
--------------------------
EOF
    exit 0
else
cat <<EOF

*** ${TESTS_FAILED} tests failed. ***
EOF
    exit 1
fi