#!/bin/sh

# Exercise "ntp-ctl force-sync": the one-shot mode that measures the offset from
# the configured sources and steps the system clock once after confirmation.
#
# It only runs interactively and steps the clock, so this test needs a pty,
# root and a real machine. The offset is injected through a gpsd-style "sock"
# source.

set -e

. debian/tests/helper-functions

workdir="${AUTOPKGTEST_TMP:-$(mktemp -d)}"
cfg="${workdir}/force-sync.toml"
sock="${workdir}/force-sync.sock"
log="${workdir}/force-sync.log"
input="${workdir}/force-sync.in"
script_pid=""

offset="4.0"

cleanup() {
    result=$?
    set +e
    [ -n "${script_pid}" ] && kill "${script_pid}" 2>/dev/null
    if [ "${result}" -ne 0 ]; then
        echo "## force-sync output:"
        cat "${log}" 2>/dev/null
    fi
}
trap cleanup EXIT

systemctl --quiet stop "${NTPD_RS_SERVICE}" 2>/dev/null || :

cat > "${cfg}" <<EOF
[observability]
log-level = "info"

[[source]]
mode = "sock"
path = "${sock}"
precision = 1e-3

[synchronization]
minimum-agreeing-sources = 1
EOF

printf "Validating the force-sync configuration: "
ntp-ctl validate -c "${cfg}" >/dev/null 2>&1 && __test_ok || __test_fail

printf 'y\n' > "${input}"

printf "Starting force-sync under a pseudo-terminal: "
# script(1) allocates a pty for the child, satisfying force-sync's is_terminal()
script -q -e -c "ntp-ctl force-sync -c ${cfg}" "${log}" < "${input}" >/dev/null 2>&1 &
script_pid=$!
sleep 2
kill -0 "${script_pid}" 2>/dev/null && __test_ok || __test_fail

printf "Waiting for force-sync to create the sock source socket: "
ok=1
for _ in $(seq 1 30); do
    if [ -S "${sock}" ]; then
        ok=0
        break
    fi
    sleep 1
done
[ "${ok}" -eq 0 ] && __test_ok || __test_fail

printf "Injecting a %ss offset through the sock source: " "${offset}"
# gpsd-style sample layout, matching ntpd-rs's own parser in
# src/daemon/sock_source.rs. little-endian: tv_sec(i64) tv_usec(i64)
# offset(f64) pulse(i32) leap(i32) pad(4) magic(i32 = "SOCK").
send_sample() {
    python3 - "${sock}" "${offset}" <<'PY'
import socket, struct, sys
path, offset = sys.argv[1], float(sys.argv[2])
buf = struct.pack('<qqdiixxxxi', 0, 0, offset, 0, 0, 0x534F434B)
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
try:
    s.sendto(buf, path)
finally:
    s.close()
PY
}
sent=1
for _ in $(seq 1 10); do
    send_sample 2>/dev/null && sent=0
    kill -0 "${script_pid}" 2>/dev/null || break
    sleep 1
done
[ "${sent}" -eq 0 ] && __test_ok || __test_fail

printf "Waiting for force-sync to finish: "
ok=1
for _ in $(seq 1 30); do
    if ! kill -0 "${script_pid}" 2>/dev/null; then
        ok=0
        break
    fi
    sleep 1
done
[ "${ok}" -eq 0 ] && __test_ok || __test_fail
script_pid=""

printf "Checking that force-sync detected the injected offset: "
grep -q "by 4 second" "${log}" && __test_ok || __test_fail

printf "Checking that force-sync stepped the system clock: "
grep -q "Time updated successfully" "${log}" && __test_ok || __test_fail

echo
echo "## force-sync output:"
cat "${log}"

exit 0
