#!/bin/sh

# Copyright (C) 2016-2025 Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# Exit with error if commands exit with non-zero and if undefined variables are
# used.
set -eu

# Test suite for D2
test_suite="d2"

# Include common test library.
# shellcheck source=src/lib/testutils/dhcp_test_lib.sh.in
. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"

# Path to the temporary configuration file.
CFG_FILE="@abs_top_builddir@/src/bin/shell/tests/test_config.json"

# Path to the D2 Server log file.
LOG_FILE="@abs_top_builddir@/src/bin/shell/tests/test.log"

# Set env KEA_LOG_FILE_DIR to override default log path.
export KEA_LOG_FILE_DIR="@abs_top_builddir@/src/bin/shell/tests"

# Path to the test certificate authority directory.
TEST_CA_DIR="@abs_top_srcdir@/src/lib/asiolink/testutils/ca"

# Configuration without TLS.
CONFIG_NONE="{
    \"DhcpDdns\":
    {
        \"control-sockets\": [
        {
            \"socket-type\": \"http\",
            \"socket-address\": \"127.0.0.1\",
            \"socket-port\": 8443
        }
        ],
        \"loggers\": [
        {
            \"name\": \"kea-dhcp-ddns\",
            \"output-options\": [
                {
                    \"output\": \"${LOG_FILE}\"
                }
            ],
            \"severity\": \"DEBUG\"
        }
        ]
    }
}"

# Configuration without mutual authentication i.e. only channel protection.
CONFIG_NOCR="{
    \"DhcpDdns\":
    {
        \"control-sockets\": [
        {
            \"socket-type\": \"http\",
            \"socket-address\": \"127.0.0.1\",
            \"socket-port\": 8443,
            \"trust-anchor\": \"${TEST_CA_DIR}/kea-ca.crt\",
            \"cert-file\": \"${TEST_CA_DIR}/kea-server-addr.crt\",
            \"key-file\": \"${TEST_CA_DIR}/kea-server.key\",
            \"cert-required\": false
        }
        ],
        \"loggers\": [
        {
            \"name\": \"kea-dhcp-ddns\",
            \"output-options\": [
                {
                    \"output\": \"${LOG_FILE}\"
                }
            ],
            \"severity\": \"DEBUG\"
        }
        ]
    }
}"

# Configuration with mutual authentication.
CONFIG="{
    \"DhcpDdns\":
    {
        \"control-sockets\": [
        {
            \"socket-type\": \"http\",
            \"socket-address\": \"127.0.0.1\",
            \"socket-port\": 8443,
            \"trust-anchor\": \"${TEST_CA_DIR}/kea-ca.crt\",
            \"cert-file\": \"${TEST_CA_DIR}/kea-server-addr.crt\",
            \"key-file\": \"${TEST_CA_DIR}/kea-server.key\",
            \"cert-required\": true
        }
        ],
        \"loggers\": [
        {
            \"name\": \"kea-dhcp-ddns\",
            \"output-options\": [
                {
                    \"output\": \"${LOG_FILE}\"
                }
            ],
            \"severity\": \"DEBUG\"
        }
        ]
    }
}"

# In these tests we need to use two binaries: D2 Server and Kea shell.
# Using bin and bin_path would be confusing, so we omit defining bin
# and bin_path on purpose.
d2_bin="kea-dhcp-ddns"
d2_bin_path="@abs_top_builddir@/src/bin/d2"

shell_bin="kea-shell"
shell_bin_path="@abs_top_builddir@/src/bin/shell"

tmpfile_path="@abs_top_builddir@/src/bin/shell/tests"

list_commands_test() {
    local test_name="${1}"
    local config="${2}"
    local arguments="${3}"
    local expected_response="${4}"

    # Setup phase: start D2.

    # Log the start of the test and print test name.
    test_start "${test_suite}-${test_name}"

    # Create correct configuration file.
    create_config "${config}"

    # Instruct D2 Server to log to the specific file.
    set_logger

    # Start D2 Server
    start_kea ${d2_bin_path}/${d2_bin}

    # Wait up to 20s for D2 Server to start.
    wait_for_kea 20
    if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
        printf "ERROR: timeout waiting for D2 Server to start.\n"
        clean_exit 1
    fi

    # Check if it is still running. It could have terminated (e.g. as a result
    # of configuration failure).
    get_pid ${d2_bin}
    if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
        printf "ERROR: expected one D2 Server process to be started. \
 Found %d processes started.\n" "${_GET_PIDS_NUM}"
        clean_exit 1
    fi

    # Check in the log file, how many times server has been configured.
    # It should be just once on startup.
    get_reconfigs
    if [ "${_GET_RECONFIGS}" -ne 1 ]; then
        printf 'ERROR: server been configured %s time(s), but exactly 1 was expected.\n' "${_GET_RECONFIGS}"
        clean_exit 1
    else
        printf "Server successfully configured.\n"
    fi

    # Main test phase: send command, check response.

    # shellcheck disable=SC2086
    # SC2086: Double quote to prevent globbing and word splitting.
    # Reason: we specifically want ${arguments} to split because there may be multiple words in it.
    tmp="echo | ${shell_bin_path}/${shell_bin} --port 8443 \
    ${arguments} > ${tmpfile_path}/shell-stdout.txt"
    echo "Executing kea-shell ($tmp)"

    # shellcheck disable=SC2086
    # SC2086: Double quote to prevent globbing and word splitting.
    # Reason: we specifically want ${arguments} to split because there may be multiple words in it.
    echo | ${shell_bin_path}/${shell_bin} --port 8443 \
    ${arguments} > ${tmpfile_path}/shell-stdout.txt
    EXIT_CODE=$?

    # Check the exit code
    if [ "${EXIT_CODE}" -ne 0 ]; then
        echo "ERROR: kea-shell returned ${EXIT_CODE} exit code, expected 0."
    else
        echo "kea-shell returned ${EXIT_CODE} exit code as expected."
    fi

    # Now check the response
    rm -f ${tmpfile_path}/shell-expected.txt
    printf '%s\n' "${expected_response}" > ${tmpfile_path}/shell-expected.txt
    diff ${tmpfile_path}/shell-stdout.txt ${tmpfile_path}/shell-expected.txt
    diff_code=$?
    if [ "${diff_code}" -ne 0 ]; then
        echo "ERROR:" \
        "content returned is different than expected." \
        "See ${tmpfile_path}/shell-*.txt"
        echo "EXPECTED:"
        cat ${tmpfile_path}/shell-expected.txt
        echo "ACTUAL RESULT:"
        cat ${tmpfile_path}/shell-stdout.txt
        clean_exit 1
    else
        echo "Content returned by kea-shell meets expectation."
        rm ${tmpfile_path}/shell-*.txt
    fi
    # Main test phase ends.

    # Cleanup phase: shutdown D2 Server

    # Send SIGTERM signal to D2 Server
    send_signal 15 ${d2_bin}

    # Now wait for process to log that it is exiting.
    wait_for_message 10 "DCTL_SHUTDOWN" 1
    if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
        printf "ERROR: D2 Server did not log shutdown.\n"
        clean_exit 1
    fi

    # Make sure the server is down.
    wait_for_server_down 5 ${d2_bin}
    assert_eq 1 "${_WAIT_FOR_SERVER_DOWN}" \
        "Expected wait_for_server_down return %d, returned %d"

    test_finish 0
}

list_commands_test "NoTLS" "${CONFIG_NONE}" "" \
"[ { \"arguments\": [ \"build-report\", \"config-get\", \"config-hash-get\", \"config-reload\", \"config-set\", \"config-test\", \"config-write\", \"list-commands\", \"shutdown\", \"statistic-get\", \"statistic-get-all\", \"statistic-reset\", \"statistic-reset-all\", \"status-get\", \"version-get\" ], \"result\": 0 } ]"
list_commands_test "Encrypted" "${CONFIG_NOCR}" \
"--ca ${TEST_CA_DIR}/kea-ca.crt" \
"[ { \"arguments\": [ \"build-report\", \"config-get\", \"config-hash-get\", \"config-reload\", \"config-set\", \"config-test\", \"config-write\", \"list-commands\", \"shutdown\", \"statistic-get\", \"statistic-get-all\", \"statistic-reset\", \"statistic-reset-all\", \"status-get\", \"version-get\" ], \"result\": 0 } ]"
list_commands_test "Authenticated" "${CONFIG}" \
"--ca ${TEST_CA_DIR}/kea-ca.crt --cert ${TEST_CA_DIR}/kea-client.crt --key ${TEST_CA_DIR}/kea-client.key" \
"[ { \"arguments\": [ \"build-report\", \"config-get\", \"config-hash-get\", \"config-reload\", \"config-set\", \"config-test\", \"config-write\", \"list-commands\", \"shutdown\", \"statistic-get\", \"statistic-get-all\", \"statistic-reset\", \"statistic-reset-all\", \"status-get\", \"version-get\" ], \"result\": 0 } ]"
