diff options
Diffstat (limited to 'test')
115 files changed, 10735 insertions, 0 deletions
diff --git a/test/compilation/001-features b/test/compilation/001-features new file mode 100755 index 0000000..9bd340f --- /dev/null +++ b/test/compilation/001-features @@ -0,0 +1,36 @@ +#!/bin/sh + +# Try to compile chrony in various combinations of disabled features + +cd ../.. + +export CFLAGS="-O2 -Werror -Wpointer-arith -Wformat-signedness -Wno-unknown-warning-option -D_FORTIFY_SOURCE=2" + +for opts in \ + "--enable-debug" \ + "--enable-ntp-signd" \ + "--enable-scfilter" \ + "--disable-asyncdns" \ + "--disable-ipv6" \ + "--disable-privdrop" \ + "--disable-readline" \ + "--disable-rtc" \ + "--disable-sechash" \ + "--disable-cmdmon" \ + "--disable-cmdmon --enable-scfilter" \ + "--disable-ntp" \ + "--disable-ntp --enable-scfilter" \ + "--disable-nts" \ + "--disable-refclock" \ + "--disable-timestamping" \ + "--disable-timestamping --disable-ntp" \ + "--disable-cmdmon --disable-ntp" \ + "--disable-cmdmon --disable-ntp --enable-scfilter" \ + "--disable-cmdmon --disable-refclock" \ + "--disable-cmdmon --disable-ntp --disable-refclock" +do + ./configure $opts || exit 1 + make clean + make "$@" || exit 1 + make -C test/unit check || exit 1 +done diff --git a/test/compilation/002-scanbuild b/test/compilation/002-scanbuild new file mode 100755 index 0000000..35a3faf --- /dev/null +++ b/test/compilation/002-scanbuild @@ -0,0 +1,16 @@ +#!/bin/sh + +cd ../.. + +for opts in \ + "--host-system=Linux" \ + "--host-system=NetBSD" \ + "--host-system=FreeBSD" \ + "--without-nettle" \ + "--without-nettle --without-nss" \ + "--without-nettle --without-nss --without-tomcrypt" \ + "--without-nettle --without-nss --without-tomcrypt --without-gnutls" +do + ./configure $opts + scan-build make "$@" || exit 1 +done diff --git a/test/compilation/003-sanitizers b/test/compilation/003-sanitizers new file mode 100755 index 0000000..8040efe --- /dev/null +++ b/test/compilation/003-sanitizers @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# Run the unit and simulation tests with different compiler sanitizers +# and under valgrind + +valgrind_opts="--leak-check=full --errors-for-leak-kinds=definite" + +cd ../.. + +if [ "$(uname -sm)" != "Linux x86_64" ]; then + echo Test supported on Linux x86_64 only + exit 1 +fi + +[ -f /etc/os-release ] && . /etc/os-release + +if [ "$ID" = "fedora" ]; then + echo Checking test dependencies: + rpm -q {gcc,clang}.x86_64 {valgrind,libgcc,clang-libs}.{x86_64,i686} || exit 1 + rpm -q {libseccomp,nettle,nss-softokn-freebl,libtomcrypt,gnutls}-devel.{x86_64,i686} || exit 1 + echo +fi + +touch Makefile + +for extra_config_opts in \ + "--all-privops" \ + "--disable-ipv6" \ + "--disable-scfilter" \ + "--without-gnutls" \ + "--without-nettle" \ + "--without-nettle --without-nss" \ + "--without-nettle --without-nss --without-tomcrypt" \ + "--without-nettle --without-nss --without-tomcrypt --without-gnutls"; \ +do + for arch_opts in "-m32" ""; do + pushd test/simulation/clknetsim || exit 1 + make clean > /dev/null 2>&1 + CFLAGS="$arch_opts -DCLKNETSIM_DISABLE_SYSCALL" make "$@" || exit 1 + echo + + popd + + for CC in gcc clang; do + export CC + + for san_options in "" "-fsanitize=address" "-fsanitize=memory"; do + export CFLAGS="-O2 -g -fsanitize=undefined -fsanitize=float-divide-by-zero -fno-sanitize-recover=undefined,float-divide-by-zero $san_options $arch_opts" + + # clang msan doesn't work on i686 and otherwise requires patches + echo $CFLAGS | grep -q 'sanitize=memory' && continue + + [ -n "$TEST_NO_M32_CLANG" -a "$arch_opts" = "-m32" -a "$CC" = "clang" ] && continue + + [ -n "$TEST_GCC_STATIC_ASAN" -a "$CC" = "gcc" ] && + echo $CFLAGS | grep -q 'sanitize=address' && CFLAGS="$CFLAGS -static-libasan" + + config_opts="--with-user=chrony --with-ntp-era=1000000000 --enable-debug --enable-scfilter --enable-ntp-signd $extra_config_opts" + + echo ----------------------------------------------------------------------------- + echo CC=\"$CC\" CFLAGS=\"$CFLAGS\" ./configure $config_opts + + make distclean > /dev/null 2>&1 + + ./configure $config_opts || exit 1 + + if echo "$config_opts" | grep -q all-privops; then + for op in ADJUSTTIME ADJUSTTIMEX SETTIME BINDSOCKET; do + echo "#define PRIVOPS_$op 1" >> config.h + done + fi + + make "$@" || exit 1 + + [ -n "$TEST_BUILD_ONLY" ] && continue + + echo + pushd test/unit || exit 1 + make "$@" || exit 1 + if [ "$san_options" = "" ]; then + make check TEST_WRAPPER="valgrind $valgrind_opts --error-exitcode=1" || exit 1 + else + make check || exit 1 + fi + popd + + [ -n "$TEST_UNIT_ONLY" ] && continue + + echo + pushd test/simulation || exit 1 + export CLKNETSIM_RANDOM_SEED=101 + if [ "$arch_opts" = "" -a "$san_options" = "" ]; then + CLKNETSIM_CLIENT_WRAPPER="valgrind $valgrind_opts" ./run -i 1 || exit 1 + elif [ "$CC" = "gcc" ] && ! echo $CFLAGS | grep -q "-static-libasan"; then + libasan=$(ldd ../../chronyd | grep -o '/.*lib.*/libasan.so.[0-9]') + CLKNETSIM_PRELOAD=$libasan ./run -i 1 || exit 1 + else + ./run -i 1 || exit 1 + fi + popd + done + done + done +done diff --git a/test/kernel/Makefile b/test/kernel/Makefile new file mode 100644 index 0000000..6ec8341 --- /dev/null +++ b/test/kernel/Makefile @@ -0,0 +1,7 @@ +CFLAGS=-O2 -Wall +PROGS=adjtime ntpadjtime + +all: $(PROGS) + +clean: + rm -f $(PROGS) diff --git a/test/kernel/adjtime.c b/test/kernel/adjtime.c new file mode 100644 index 0000000..0ca8ff2 --- /dev/null +++ b/test/kernel/adjtime.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) Miroslav Lichvar 2015 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* Test the system adjtime() function. Check the range of supported offset, + support for readonly operation, and slew rate with different update + intervals and offsets. */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/time.h> +#include <unistd.h> + +static int +diff_tv(struct timeval *tv1, struct timeval *tv2) +{ + return 1000000 * (tv1->tv_sec - tv2->tv_sec) + (tv1->tv_usec - tv2->tv_usec); +} + +static struct timeval +usec_to_tv(int usec) +{ + struct timeval tv; + + tv.tv_sec = usec / 1000000; + tv.tv_usec = usec % 1000000; + + return tv; +} + +static int +try_adjtime(struct timeval *new, struct timeval *old) +{ + int r; + + r = adjtime(new, old); + if (r) + printf("adjtime() failed : %s ", strerror(errno)); + return r; +} + +static void +reset_adjtime(void) +{ + struct timeval tv; + + tv = usec_to_tv(0); + try_adjtime(&tv, NULL); +} + +static void +test_range(void) +{ + struct timeval tv; + int i; + + printf("range:\n"); + + for (i = 0; i < sizeof (time_t) * 8; i++) { + tv.tv_usec = 0; + tv.tv_sec = (1ULL << i) - 1; + printf("%20lld s : ", (long long)tv.tv_sec); + printf("%s\n", !try_adjtime(&tv, NULL) ? "ok" : ""); + tv.tv_sec = ~tv.tv_sec; + printf("%20lld s : ", (long long)tv.tv_sec); + printf("%s\n", !try_adjtime(&tv, NULL) ? "ok" : ""); + } +} + +static void +test_readonly(void) +{ + struct timeval tv1, tv2; + int i, r; + + printf("readonly:\n"); + + for (i = 0; i <= 20; i++) { + tv1 = usec_to_tv(1 << i); + + printf("%9d us : ", 1 << i); + try_adjtime(&tv1, NULL); + r = !try_adjtime(NULL, &tv2) && !diff_tv(&tv1, &tv2); + printf("%s\n", r ? "ok" : "fail"); + } +} + +static void +test_readwrite(void) +{ + struct timeval tv1, tv2, tv3; + int i, r; + + printf("readwrite:\n"); + + for (i = 0; i <= 20; i++) { + tv1 = usec_to_tv(1 << i); + tv3 = usec_to_tv(0); + + printf("%9d us : ", 1 << i); + try_adjtime(&tv1, NULL); + r = !try_adjtime(&tv3, &tv2) && !diff_tv(&tv1, &tv2); + printf("%s\n", r ? "ok" : "fail"); + } +} + +static void +xusleep(int usec) +{ + struct timeval tv; + + tv = usec_to_tv(usec); + select(0, NULL, NULL, NULL, &tv); +} + +static void +test_slew(void) +{ + struct timeval tv1, tv2, tv3; + int i, j, k, diff, min, has_min; + + printf("slew:\n"); + + for (i = 9; i <= 20; i++) { + printf("%9d us : ", 1 << i); + for (j = 4; j <= 20; j += 4) { + for (min = has_min = 0, k = 4; k < 16; k += 2) { + + tv1 = usec_to_tv(1 << j); + tv3 = usec_to_tv(0); + + xusleep(1 << i); + reset_adjtime(); + + xusleep(1 << i); + if (try_adjtime(&tv1, NULL)) + continue; + + xusleep(1 << i); + if (try_adjtime(&tv3, &tv2)) + continue; + + diff = diff_tv(&tv1, &tv2); + if (!has_min || min > diff) { + min = diff; + has_min = 1; + } + } + + if (!has_min) + continue; + + printf(" %5d (%d)", min, 1 << j); + fflush(stdout); + } + printf("\n"); + } +} + +int +main() +{ + test_range(); + test_readonly(); + test_readwrite(); + test_slew(); + + reset_adjtime(); + + return 0; +} diff --git a/test/kernel/ntpadjtime.c b/test/kernel/ntpadjtime.c new file mode 100644 index 0000000..4af96b4 --- /dev/null +++ b/test/kernel/ntpadjtime.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) Miroslav Lichvar 2015 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* Check the frequency range of the system ntp_adjtime() implementation */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/time.h> +#include <sys/timex.h> + +static int +try_ntpadjtime(struct timex *t) +{ + int r; + r = ntp_adjtime(t); + if (r < 0) + printf("ntp_adjtime() failed : %s ", strerror(errno)); + return r; +} + +static void +reset_ntpadjtime(void) +{ + struct timex t; + + t.modes = MOD_OFFSET | MOD_FREQUENCY; + t.offset = 0; + t.freq = 0; + try_ntpadjtime(&t); +} + +static void +test_freqrange(void) +{ + struct timex t; + int i; + + printf("freq range:\n"); + + for (i = -1000; i <= 1000; i += 50) { + t.modes = MOD_FREQUENCY; + t.freq = i * (1 << 16); + printf("%4d ppm => ", i); + if (try_ntpadjtime(&t) < 0) + continue; + + printf("%4ld ppm : ", t.freq / (1 << 16)); + printf("%s\n", t.freq == i * (1 << 16) ? "ok" : "fail"); + } +} + +int +main() +{ + test_freqrange(); + + reset_ntpadjtime(); + + return 0; +} diff --git a/test/simulation/001-defaults b/test/simulation/001-defaults new file mode 100755 index 0000000..b39d95c --- /dev/null +++ b/test/simulation/001-defaults @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "default test settings" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/002-largenetwork b/test/simulation/002-largenetwork new file mode 100755 index 0000000..a9e0ad8 --- /dev/null +++ b/test/simulation/002-largenetwork @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "large network" + +time_rms_limit=5e-4 + +server_strata=3 +servers=4 +clients=5 + +client_start=2000 +min_sync_time=2100 +max_sync_time=2300 + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/003-largefreqoffset b/test/simulation/003-largefreqoffset new file mode 100755 index 0000000..9381b1a --- /dev/null +++ b/test/simulation/003-largefreqoffset @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "large frequency offset" + +max_sync_time=1000 + +for freq_offset in -5e-2 -5e-3 5e-3 5e-2; do + # Adjust offset so it's close to 0 on first clock update + time_offset=$(awk "BEGIN {print -($freq_offset * 130)}") + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/004-largetimeoffset b/test/simulation/004-largetimeoffset new file mode 100755 index 0000000..4aebdd3 --- /dev/null +++ b/test/simulation/004-largetimeoffset @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "large time offset" + +min_sync_time=1300 +max_sync_time=1400 + +for time_offset in -1e2 1e2; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/005-externalstep b/test/simulation/005-externalstep new file mode 100755 index 0000000..e6fff26 --- /dev/null +++ b/test/simulation/005-externalstep @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "external time step" + +min_sync_time=1500 +max_sync_time=1550 + +for step in -1e2 1e2; do + # Make one step in 150th second + client_step="(* $step (equal 0.1 (sum 1.0) 150))" + run_test || test_fail + check_chronyd_exit || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +min_sync_time=5120 +max_sync_time=6200 +client_conf="makestep 1 -1" + +for step in -1e8 -1e5 1e5 1e8; do + # Make one step in 5000th second + client_step="(* $step (equal 0.1 (sum 1.0) 5000))" + run_test || test_fail + check_chronyd_exit || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +min_sync_time=$default_min_sync_time +max_sync_time=$default_max_sync_time +time_max_limit=2e4 +time_rms_limit=8e3 + +for step in -1e4 1e4; do + # Make a step every 500 seconds + client_step="(* $step (equal 0.1 (% (sum 1.0) 500) 0))" + run_test || test_fail + check_chronyd_exit || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/006-largejitter b/test/simulation/006-largejitter new file mode 100755 index 0000000..36ae5e2 --- /dev/null +++ b/test/simulation/006-largejitter @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "large jitter" + +time_offset=1e0 +jitter=1e-1 + +time_max_limit=5e-1 +freq_max_limit=2e-1 +time_rms_limit=1e-1 +freq_rms_limit=5e-3 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/007-largewander b/test/simulation/007-largewander new file mode 100755 index 0000000..af0c599 --- /dev/null +++ b/test/simulation/007-largewander @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "large wander" + +wander=1e-7 + +time_max_limit=5e-3 +freq_max_limit=5e-3 +time_rms_limit=1e-3 +freq_rms_limit=1e-4 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/008-ntpera b/test/simulation/008-ntpera new file mode 100755 index 0000000..2eea63b --- /dev/null +++ b/test/simulation/008-ntpera @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "NTP eras" + +if check_config_h 'HAVE_LONG_TIME_T 1'; then + ntp_start=$(awk "BEGIN {print $(grep NTP_ERA_SPLIT ../../config.h | tr -dc '0-9*+-')}") +else + ntp_start="-2147483648" +fi + +# Set the starting test date to 500 seconds before the second NTP era. +# This should work with 32-bit time_t and also with 64-bit time_t if the +# configured NTP interval covers the test interval. +export CLKNETSIM_START_DATE=$(date -d 'Feb 7 06:19:56 UTC 2036' +'%s') + +if awk "BEGIN {exit !($ntp_start <= $CLKNETSIM_START_DATE && \ + $CLKNETSIM_START_DATE + $limit < $ntp_start + 2^32)}"; then + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +fi + +# The following tests need 64-bit time_t and ntp_start not before 1970 +check_config_h 'HAVE_LONG_TIME_T 1' || test_skip +echo "$ntp_start" | grep -q '-' && test_skip + +for time_offset in -1e-1 1e-1; do + for start_offset in 0 "2^32 - $limit"; do + export CLKNETSIM_START_DATE=$(awk "BEGIN {printf \"%.0f\", $ntp_start + $start_offset}") + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail + done + + for start_offset in -$limit "2^32"; do + export CLKNETSIM_START_DATE=$(awk "BEGIN {printf \"%.0f\", $ntp_start + $start_offset}") + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync && test_fail + done +done + +test_pass diff --git a/test/simulation/009-sourceselection b/test/simulation/009-sourceselection new file mode 100755 index 0000000..547c376 --- /dev/null +++ b/test/simulation/009-sourceselection @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "source selection" + +# Falsetickers should be detected if their number is less than half of all + +base_delay=1e-3 +servers=5 + +for falsetickers in 1 2; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +for falsetickers in 3 4; do + run_test || test_fail + check_chronyd_exit || test_fail + check_packet_interval || test_fail + # These check are expected to fail + check_source_selection && test_fail + check_sync && test_fail +done + +# Sources with large asymmetric delay should be excluded + +servers=3 +falsetickers=0 +base_delay="(+ 1e-3 (equal 0.1 to 2) (equal 0.1 to 3))" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/010-multrecv b/test/simulation/010-multrecv new file mode 100755 index 0000000..36e7476 --- /dev/null +++ b/test/simulation/010-multrecv @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +. ./test.common + +export CLKNETSIM_RECV_MULTIPLY=4 + +test_start "multiple received packets" + +limit=50000 +client_server_options="minpoll 6 maxpoll 6" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/011-asymjitter b/test/simulation/011-asymjitter new file mode 100755 index 0000000..9fb5567 --- /dev/null +++ b/test/simulation/011-asymjitter @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "asymmetric jitter" + +jitter=5e-4 +jitter_asymmetry=0.47 +limit=100000 +max_sync_time=2000 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/012-daemonts b/test/simulation/012-daemonts new file mode 100755 index 0000000..a1b90e3 --- /dev/null +++ b/test/simulation/012-daemonts @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "daemon timestamping" + +export CLKNETSIM_TIMESTAMPING=0 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/013-nameserv b/test/simulation/013-nameserv new file mode 100755 index 0000000..941026b --- /dev/null +++ b/test/simulation/013-nameserv @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "name resolving" + +dns=1 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/101-poll b/test/simulation/101-poll new file mode 100755 index 0000000..1416b22 --- /dev/null +++ b/test/simulation/101-poll @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "minpoll/maxpoll options" + +wander=0.0 +jitter=1e-6 + +time_max_limit=1e-5 +freq_max_limit=1e-5 +time_rms_limit=5e-6 +freq_rms_limit=5e-6 +client_conf="makestep 1e-2 1" + +for poll in $(seq 1 14); do + client_server_options="minpoll $poll maxpoll $poll" + limit=$[2**$poll * 10] + min_sync_time=$[2**$poll * 2] + max_sync_time=$[2**$poll * 21 / 10 + 1] + client_max_min_out_interval=$(awk "BEGIN {print 2^$poll * 1.1}") + client_min_mean_out_interval=$(awk "BEGIN {print 2^$poll * 0.99}") + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +min_sync_time=$default_min_sync_time +max_sync_time=$default_max_sync_time +client_max_min_out_interval=$default_client_max_min_out_interval +client_min_mean_out_interval=$default_client_min_mean_out_interval + +limit=10 + +for poll in $(seq -7 2 -1); do + client_server_options="minpoll $poll maxpoll $poll" + + base_delay=1e-4 + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_file_messages " 2 1 " \ + $[2**-poll * limit * 9 / 10] $[2**-poll * limit] log.packets || test_fail + + base_delay=2e-2 + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_file_messages " 2 1 " $[limit * 9 / 10] $limit log.packets || test_fail +done + +test_pass diff --git a/test/simulation/102-iburst b/test/simulation/102-iburst new file mode 100755 index 0000000..9936572 --- /dev/null +++ b/test/simulation/102-iburst @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "iburst option" + +freq_offset=1e-4 + +client_conf="makestep 1e-2 1 +driftfile tmp/drift" +client_server_options="iburst" + +min_sync_time=4 +max_sync_time=6 + +echo "100 1.0" > tmp/drift + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/103-initstepslew b/test/simulation/103-initstepslew new file mode 100755 index 0000000..fe47b68 --- /dev/null +++ b/test/simulation/103-initstepslew @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "initstepslew directive" + +freq_offset=0.0 +wander=0.0 +time_rms_limit=1e-3 +limit=100 + +client_conf="initstepslew 5 192.168.123.1" +client_server_conf="#" + +min_sync_time=6 +max_sync_time=35 + +for time_offset in -2.0 -0.2 0.2 2.0; do + run_test || test_fail + check_chronyd_exit || test_fail + check_packet_interval || test_fail + check_sync || test_fail + check_log_messages "00:00:0.Z System's initial.*slew" 1 1 || test_fail +done + +min_sync_time=5 +max_sync_time=5 + +for time_offset in -1e8 -1e2 1e2 1e8; do + run_test || test_fail + check_packet_interval || test_fail + check_sync || test_fail + check_log_messages "System's initial.*step" 1 1 || test_fail +done + +time_offset=3 +limit=500 +servers=2 +falsetickers=1 +client_conf="initstepslew 5 192.168.123.1 192.168.123.2" +client_server_conf="server 192.168.123.2" + +min_sync_time=360 +max_sync_time=450 + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +check_sync || test_fail +check_log_messages "00:03:2.Z No suitable source for initstepslew" 1 1 || test_fail + +client_conf="initstepslew 5 192.168.123.1 192.168.123.2" + +min_sync_time=1 +max_sync_time=500 +server_conf="deny all" + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +check_sync && test_fail +check_log_messages "00:00:1.Z No suitable source for initstepslew" 1 1 || test_fail + +test_pass diff --git a/test/simulation/104-driftfile b/test/simulation/104-driftfile new file mode 100755 index 0000000..93d4363 --- /dev/null +++ b/test/simulation/104-driftfile @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "driftfile directive" + +servers=0 +time_offset=0.0 +wander=0.0 +limit=10 +freq_max_limit=1e-9 +min_sync_time=1 +max_sync_time=1 +client_conf="driftfile tmp/drift" + +for freq_offset in -5e-2 -5e-4 -5e-6 5e-6 5e-4 5e-2; do + awk "BEGIN {printf \"%.9e 1\", 1e6 - 1 / (1 + $freq_offset) * 1e6}" > tmp/drift + run_test || test_fail + check_chronyd_exit || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/105-ntpauth b/test/simulation/105-ntpauth new file mode 100755 index 0000000..1f228f5 --- /dev/null +++ b/test/simulation/105-ntpauth @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "NTP authentication" + +server_conf="keyfile tmp/server.keys" +client_conf="keyfile tmp/client.keys" + +cat > tmp/server.keys <<-EOF +1 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E +2 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E +3 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E +4 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E +EOF + +cat > tmp/client.keys <<-EOF +1 k]<j.Jtw^Oo;z5E>n\_0-x=)yP\f<)Z^ +2 ASCII:k]<j.Jtw^Oo;z5E>n\_0-x=)yP\f<)Z^ +3 MD5 ASCII:k]<j.Jtw^Oo;z5E>n\_0-x=)yP\f<)Z^ +4 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E +EOF + +keys=4 + +types="MD5" +check_config_h 'FEAT_SECHASH 1' && types="$types SHA1 SHA256 SHA384 SHA512" +check_config_h 'HAVE_CMAC 1' && types="$types AES128 AES256" + +for type in $types; do + keys=$[$keys + 1] + case $type in + AES128) length=16;; + AES256) length=32;; + *) length=$[$RANDOM % 32 + 1];; + esac + + key=$(echo $keys $type HEX:$(tr -c -d '0-9A-F' < /dev/urandom 2> /dev/null | \ + head -c $[$length * 2])) + echo "$key" >> tmp/server.keys + echo "$key" >> tmp/client.keys +done + +for version in 3 4; do + for key in $(seq $keys); do + client_server_options="version $version key $key" + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail + done +done + +server_conf="" + +run_test || test_fail +check_chronyd_exit || test_fail +# This check must fail as the server doesn't know the key +check_sync && test_fail +check_packet_interval || test_fail + +server_conf="keyfile tmp/server.keys" +client_conf="" + +run_test || test_fail +check_chronyd_exit || test_fail +# This check must fail as the client doesn't know the key +check_sync && test_fail +check_packet_interval || test_fail + +client_conf="keyfile tmp/client.keys" +clients=2 +peers=2 +max_sync_time=500 +base_delay="$default_base_delay (* -1 (equal 0.1 from 3) (equal 0.1 to 1))" + +for versions in "3 3" "3 4" "4 3" "4 4"; do + for key in 1 $keys; do + client_lpeer_options="version ${versions% *} key $key" + client_rpeer_options="version ${versions#* } key $key" + run_test || test_fail + check_chronyd_exit || test_fail + check_sync || test_fail + done +done + +client_lpeer_options="key 1" +client_rpeer_options="key 2" + +run_test || test_fail +check_chronyd_exit || test_fail +# This check must fail as the peers are using different keys" +check_sync && test_fail + +test_pass diff --git a/test/simulation/106-refclock b/test/simulation/106-refclock new file mode 100755 index 0000000..f09f170 --- /dev/null +++ b/test/simulation/106-refclock @@ -0,0 +1,143 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "SHM refclock" + +check_config_h 'FEAT_REFCLOCK 1' || test_skip +check_config_h 'FEAT_PHC 1' || test_skip +check_config_h 'FEAT_CMDMON 1' || test_skip + +export CLKNETSIM_PHC_DELAY=1e-6 +export CLKNETSIM_PHC_JITTER=1e-7 + +servers=0 +limit=1000 +refclock_jitter=$jitter +min_sync_time=45 +max_sync_time=70 +chronyc_start=70 +chronyc_conf="tracking" + +for refclock in "SHM 0" "PHC /dev/ptp0" "PHC /dev/ptp0:nocrossts"; do + client_conf="refclock $refclock stratum 3 delay 1e-3 refid GPS +logdir tmp +log refclocks" + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + check_chronyc_output "^Reference ID.*47505300 \(GPS\) +Stratum.*: 4 +.* +Root delay : 0.001000000 seconds +.* +Update interval : 16\.. seconds +.*$" || test_fail + + if echo "$refclock" | grep -q 'PHC.*nocrossts'; then + check_file_messages "20.* GPS.*[0-9] N " 650 750 refclocks.log || test_fail + else + check_file_messages "20.* GPS.*[0-9] N " 997 1001 refclocks.log || test_fail + fi + check_file_messages "20.* GPS.*- N " 61 63 refclocks.log || test_fail + rm -f tmp/refclocks.log +done + +if check_config_h 'FEAT_PPS 1'; then + refclock_offset=0.35 + refclock_jitter=0.05 + + client_conf=" +refclock SHM 0 refid NMEA noselect +refclock PPS /dev/pps0 lock NMEA +logdir tmp +log refclocks" + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + check_chronyc_output "^Reference ID.*50505331 \(PPS1\) +Stratum.*: 1 +.* +Root delay : 0\.000000001 seconds +.*$" || test_fail + + check_file_messages "20.* PPS1.*[0-9] N " 620 740 refclocks.log || test_fail + check_file_messages "20.* PPS1.*- N " 60 63 refclocks.log || test_fail + rm -f tmp/refclocks.log + + client_conf=" +refclock SHM 0 noselect +refclock PPS /dev/pps0 +local +logdir tmp +log refclocks" + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + check_chronyc_output "^Reference ID.*50505331 \(PPS1\) +Stratum.*: 10 +.* +Root delay : 0\.000000001 seconds +.*$" || test_fail + + check_file_messages "20.* PPS1.*[0-9] N " 997 1001 refclocks.log || test_fail + check_file_messages "20.* PPS1.*- N " 60 63 refclocks.log || test_fail + rm -f tmp/refclocks.log + + min_sync_time=100 + max_sync_time=220 + chronyc_start=220 + client_conf=" +refclock SHM 0 refid NMEA offset 0.35 delay 0.1 +refclock PPS /dev/pps0 +logdir tmp +log refclocks" + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + check_chronyc_output "^Reference ID.*50505331 \(PPS1\) +Stratum.*: 1 +.* +Root delay : 0\.000000001 seconds +.*$" || test_fail + + check_file_messages "20.* PPS1.*[0-9] N " 800 940 refclocks.log || test_fail + check_file_messages "20.* PPS1.*- N " 50 63 refclocks.log || test_fail + rm -f tmp/refclocks.log +fi + +refclock_offset="(+ 0.399 (sum 1e-3))" +refclock_jitter=1e-6 +servers=1 +freq_offset="(* 1e-4 (sine 1000))" +base_delay="(* -1.0 (equal 0.1 (min time 5000) 5000))" +client_server_options="minpoll 4 maxpoll 4 filter 5 minsamples 64" +client_conf=" +refclock PHC /dev/ptp0 local poll 2 +logdir tmp +log refclocks tracking" +chronyc_conf="" +limit=10000 +max_sync_time=5000 +time_max_limit=1e-3 +time_rms_limit=5e-4 +freq_max_limit=2e-5 +freq_rms_limit=5e-6 + +run_test || test_fail +check_chronyd_exit || test_fail +check_sync || test_fail + +check_file_messages "20.* PHC0 .* [0-9] ? " 9999 10001 refclocks.log || test_fail +check_file_messages "20.* PHC0 .* - ? " 2499 2501 refclocks.log || test_fail +check_file_messages "20.* PHC0 " 0 0 tracking.log || test_fail +rm -f tmp/refclocks.log tmp/tracking.log + +test_pass diff --git a/test/simulation/107-allowdeny b/test/simulation/107-allowdeny new file mode 100755 index 0000000..4665337 --- /dev/null +++ b/test/simulation/107-allowdeny @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "allow/deny directives" + +check_config_h 'FEAT_CMDMON 1' || test_skip + +limit=500 + +# Note that start_client in clknetsim.bash always adds allow to the config + +for server_conf in \ + "deny" \ + "deny all" \ + "deny 192.168.0.0/16" \ + "deny 192.168.123" \ + "deny 192.168.123.2" \ + "deny all +allow 192.168.124.0/24" +do + run_test || test_fail + check_chronyd_exit || test_fail + check_packet_interval || test_fail + # These checks are expected to fail + check_source_selection && test_fail + check_sync && test_fail +done + +for server_conf in \ + "deny all +allow" \ + "deny all +allow all" \ + "deny all +allow 192.168.123" \ + "deny all +allow 192.168.123/24" \ + "deny 192.168.124.0/24" +do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/108-peer b/test/simulation/108-peer new file mode 100755 index 0000000..906de17 --- /dev/null +++ b/test/simulation/108-peer @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "NTP peers" + +# Allow and drop packets to the server in 1000 second intervals, so only one +# client has access to it and the other is forced to switch to the peer. +base_delay=$(cat <<-EOF | tr -d '\n' + (+ 1e-4 + (* -1 + (equal 0.1 from 2) + (equal 0.1 to 1) + (equal 0.1 (min (% time 2000) 1000) 1000)) + (* -1 + (equal 0.1 from 3) + (equal 0.1 to 1) + (equal 0.1 (max (% time 2000) 1000) 1000))) +EOF +) + +clients=2 +peers=2 +max_sync_time=1000 +client_server_options="minpoll 6 maxpoll 6" +client_peer_options="minpoll 6 maxpoll 6" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))" +client_peer_options="" + +while read lminpoll lmaxpoll rminpoll rmaxpoll max_sync_time; do + client_lpeer_options="minpoll $lminpoll maxpoll $lmaxpoll" + client_rpeer_options="minpoll $rminpoll maxpoll $rmaxpoll" + limit=$[$max_sync_time * 10] + + run_test || test_fail + check_chronyd_exit || test_fail + check_sync || test_fail +done <<-EOF + 3 6 3 6 400 + 3 3 6 6 450 + 6 6 3 3 450 + 3 6 6 6 450 + 6 6 3 6 450 + -2 -2 2 2 220 + 2 2 -2 -2 220 +EOF + +test_pass diff --git a/test/simulation/109-makestep b/test/simulation/109-makestep new file mode 100755 index 0000000..78d8d59 --- /dev/null +++ b/test/simulation/109-makestep @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "makestep directive" + +client_conf="makestep 0 -1 +corrtimeratio 1e10" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +limit=200 +jitter=1e-5 +client_conf="makestep 2 1" + +min_sync_time=130 +max_sync_time=150 + +for time_offset in -1.0 -0.1 0.1 1.0; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +min_sync_time=120 +max_sync_time=140 + +for time_offset in -1e8 -1e2 1e2 1e8; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/110-chronyc b/test/simulation/110-chronyc new file mode 100755 index 0000000..b78f0d8 --- /dev/null +++ b/test/simulation/110-chronyc @@ -0,0 +1,496 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "chronyc" + +check_config_h 'FEAT_REFCLOCK 1' || test_skip +check_config_h 'FEAT_CMDMON 1' || test_skip + +refclock_jitter=$jitter +client_server_conf=" +server node1.net1.clk +server 192.168.123.2" +client_conf=" +refclock SHM 0 noselect +smoothtime 400 0.001 leaponly" +cmdmon_unix=0 + +chronyc_conf="activity +tracking +sourcename 192.168.123.1 +sourcename 192.168.123.2 +sources +sourcestats +manual list +smoothing +waitsync +rtcdata" + +run_test || test_fail +check_chronyd_exit || test_fail + +check_chronyc_output "^200 OK +2 sources online +0 sources offline +0 sources doing burst \(return to online\) +0 sources doing burst \(return to offline\) +0 sources with unknown address +Reference ID : C0A87B01 \(192\.168\.123\.1\) +Stratum : 2 +Ref time \(UTC\) : Fri Jan 01 00:1.:.. 2010 +System time : 0\.0000..... seconds (slow|fast) of NTP time +Last offset : [+-]0\.000...... seconds +RMS offset : 0\.000...... seconds +Frequency : (99|100)\.... ppm fast +Residual freq : [+-][0-9]\.... ppm +Skew : [0-9]\.... ppm +Root delay : 0\.000...... seconds +Root dispersion : 0\.000...... seconds +Update interval : [0-9]+\.. seconds +Leap status : Normal +node1\.net1\.clk +192\.168\.123\.2 +MS Name/IP address Stratum Poll Reach LastRx Last sample +=============================================================================== +#\? SHM0 0 4 377 [0-9]+ [0-9 +-]+[un]s\[[0-9 +-]+[un]s\] \+/-[ 0-9]+[un]s +\^\* 192\.168\.123\.1 1 [67] 377 [0-9]+ [0-9 +-]+[un]s\[[0-9 +-]+[un]s\] \+/-[ 0-9]+[un]s +\^\? 192\.168\.123\.2 0 [0-9]+ 0 - \+0ns\[ \+0ns\] \+/- 0ns +Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev +============================================================================== +SHM0 [0-9 ]+ [0-9 ]+ [0-9 ]+ [ +-][01]\.... [0-9 ]+\.... [0-9 +-]+[un]s [0-9 ]+[un]s +192\.168\.123\.1 [0-9 ]+ [0-9 ]+ [0-9 ]+ [ +-][01]\.... [0-9 ]+\.... [0-9 +-]+[un]s [0-9 ]+[un]s +192\.168\.123\.2 0 0 0 \+0\.000 2000\.000 \+0ns 4000ms +210 n_samples = 0 +# Date Time\(UTC\) Slewed Original Residual +======================================================= +Active : Yes \(leap second only\) +Offset : \+0\.000000000 seconds +Frequency : \+0\.000000 ppm +Wander : \+0\.000000 ppm per second +Last update : [0-9]+\.. seconds ago +Remaining time : 0\.0 seconds +try: 1, refid: C0A87B01, correction: 0\.000......, skew: .\.... +513 RTC driver not running$" \ +|| test_fail + +chronyc_conf="tracking" +dns=1 + +run_test || test_fail +check_chronyd_exit || test_fail + +check_chronyc_output "^Reference ID : C0A87B01 \(node1\.net1\.clk\)" \ + || test_fail + +chronyc_options="-c" + +run_test || test_fail +check_chronyd_exit || test_fail + +check_chronyc_output "^C0A87B01,192\.168\.123\.1,2,12623049..\..........,-?0\.0000.....,-?0\.000......,0\.000......,(99|100)\....,-?[0-9]\....,[0-9]\....,0\.000......,0\.000......,[0-9]+\..,Normal$" \ + || test_fail + +chronyc_options="" +server_strata=0 +chronyc_start=0.5 +client_server_conf="" +client_conf="" +server_conf="server 192.168.123.1" +limit=1 + +for chronyc_conf in \ + "accheck 1.2.3.4" \ + "add peer 10.0.0.0 minpoll 2 maxpoll 6" \ + "add server 10.0.0.0 minpoll 6 maxpoll 10 iburst burst key 1 certset 2 maxdelay 1e-3 maxdelayratio 10.0 maxdelaydevratio 10.0 maxdelayquant 0.5 mindelay 1e-4 asymmetry 0.5 offset 1e-5 minsamples 6 maxsamples 6 filter 3 offline auto_offline prefer noselect trust require xleave polltarget 20 port 123 presend 7 minstratum 3 version 4 nts ntsport 4460 copy extfield F323" \ + "add server node1.net1.clk" \ + "allow 1.2.3.4" \ + "allow 1.2" \ + "allow 3.4.5" \ + "allow 6.7.8/22" \ + "allow 6.7.8.9/22" \ + "allow 0/0" \ + "allow" \ + "allow all 10/24" \ + "authdata" \ + "burst 5/10" \ + "burst 3/5 255.255.255.0/1.2.3.0" \ + "burst 1/2 1.2.3.0/24" \ + "clients" \ + "clients -k" \ + "clients -p 100" \ + "clients -r" \ + "cmdaccheck 1.2.3.4" \ + "cmdallow 1.2.3.4" \ + "cmdallow all 1.2.3.0/24" \ + "cmddeny 1.2.3.4" \ + "cmddeny all 1.2.3.0/24" \ + "cyclelogs" \ + "delete 10.0.0.0" \ + "delete ID#0000000001" \ + "deny 1.2.3.4" \ + "deny all 1.2.3.0/24" \ + "dfreq 1.0e-3" \ + "doffset -1.0" \ + "dump" \ + "local stratum 5 distance 1.0 orphan" \ + "local off" \ + "makestep 10.0 3" \ + "makestep" \ + "manual delete 0" \ + "manual off" \ + "manual on" \ + "manual reset" \ + "maxdelay 1.2.3.4 1e-2" \ + "maxdelaydevratio 1.2.3.4 5.0" \ + "maxdelayratio 1.2.3.4 3.0" \ + "maxpoll 1.2.3.4 5" \ + "maxupdateskew 1.2.3.4 10.0" \ + "minpoll 1.2.3.4 3" \ + "minstratum 1.2.3.4 1" \ + "minstratum ID#0000000001 1" \ + "ntpdata 1.2.3.4" \ + "offline" \ + "offline 255.255.255.0/1.2.3.0" \ + "offline 1.2.3.0/24" \ + "online" \ + "online 1.2.3.0/24" \ + "onoffline" \ + "polltarget 1.2.3.4 10" \ + "refresh" \ + "rekey" \ + "reload sources" \ + "reselect" \ + "reselectdist 1e-3" \ + "reset sources" \ + "selectdata" \ + "settime 16:30" \ + "settime 16:30:05" \ + "settime Nov 21, 2015 16:30:05" \ + "serverstats" \ + "shutdown" \ + "smoothtime reset" \ + "smoothtime activate" \ + "trimrtc" \ + "writertc" +do + run_test || test_fail + check_chronyd_exit || test_fail + check_chronyc_output "501 Not authorised$" || test_fail +done + +cmdmon_unix=1 + +chronyc_conf=" +authdata +clients -k -p 2 +clients -r +clients +ntpdata +selectdata +serverstats" + +run_test || test_fail +check_chronyd_exit || test_fail + +check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen +========================================================================= +node1\.net1\.clk - 0 0 0 - 0 0 0 0 +Hostname NTP Drop Int IntL Last NTS-KE Drop Int Last +=============================================================================== +Hostname NTP Drop Int IntL Last Cmd Drop Int Last +=============================================================================== +node1\.net1\.clk 1 0 - - 0 0 0 - - +Hostname NTP Drop Int IntL Last Cmd Drop Int Last +=============================================================================== +node1\.net1\.clk 0 0 - - 0 0 0 - - + +Remote address : 192\.168\.123\.1 \(C0A87B01\) +Remote port : 123 +Local address : 192\.168\.123\.1 \(C0A87B01\) +Leap status : Normal +Version : 4 +Mode : Server +Stratum : 1 +Poll interval : 6 \(64 seconds\) +Precision : -23 \(0\.000000119 seconds\) +Root delay : 0\.000000 seconds +Root dispersion : 0\.000000 seconds +Reference ID : 7F7F0101 \(\) +Reference time : Thu Dec 31 23:59:5[89] 2009 +Offset : [-+]0\.000...... seconds +Peer delay : 0\.00....... seconds +Peer dispersion : 0\.00000.... seconds +Response time : 0\.000000... seconds +Jitter asymmetry: \+0\.00 +NTP tests : 111 111 1110 +Interleaved : No +Authenticated : No +TX timestamping : Kernel +RX timestamping : Kernel +Total TX : 1 +Total RX : 1 +Total valid RX : 1 +Total good RX : 0 +S Name/IP Address Auth COpts EOpts Last Score Interval Leap +======================================================================= +M node1\.net1\.clk N ----- ----- 0 1\.0 \+0ns \+0ns N +NTP packets received : 1 +NTP packets dropped : 0 +Command packets received : 12 +Command packets dropped : 0 +Client log records dropped : 0 +NTS-KE connections accepted: 0 +NTS-KE connections dropped : 0 +Authenticated NTP packets : 0 +Interleaved NTP packets : 0 +NTP timestamps held : 0 +NTP timestamp span : 0$" || test_fail + +chronyc_conf=" +deny all +cmdallow all +allow 1.2.3.4 +allow 1.2.3.0/28 +deny 1.2.3.0/27 +allow 1.2.4.5 +deny all 1.2.4.0/27 +cmddeny 5.6.7.8 +cmdallow all 5.6.7.0/28 +accheck 1.2.3.4 +accheck 1.2.3.5 +accheck 1.2.4.5 +cmdaccheck 5.6.7.8" + +run_test || test_fail +check_chronyd_exit || test_fail + +check_chronyc_output "^200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +208 Access allowed +209 Access denied +209 Access denied +208 Access allowed$" || test_fail + +if check_config_h 'FEAT_IPV6 1'; then + chronyc_conf=" + deny all + cmdallow all + allow 2001:db8::1 + allow 2001:db8::/64 + deny 2001:db8::/63 + allow 2001:db8:1::1 + deny all 2001:db8:1::/63 + cmddeny 2001:db9::1 + cmdallow all 2001:db9::/64 + accheck 2001:db8::1 + accheck 2001:db8::2 + accheck 2001:db8:1::1 + cmdaccheck 2001:db9::1" + + run_test || test_fail + check_chronyd_exit || test_fail + + check_chronyc_output "^200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +208 Access allowed +209 Access denied +209 Access denied +208 Access allowed$" || test_fail +fi + +chronyc_conf=" +delete 192.168.123.1 +add server node1.net1.clk minpoll 6 maxpoll 10 iburst +offline 192.168.123.1 +burst 1/1 192.168.123.1 +online 192.168.123.1 +maxdelay 192.168.123.1 1e-2 +maxdelaydevratio 192.168.123.1 5.0 +maxdelayratio 192.168.123.1 3.0 +maxpoll 192.168.123.1 5 +maxupdateskew 192.168.123.1 10.0 +minpoll 192.168.123.1 3 +minstratum 192.168.123.1 1 +polltarget 192.168.123.1 10 +delete 192.168.123.1" + +run_test || test_fail +check_chronyd_exit || test_fail + +check_chronyc_output "^200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK$" || test_fail + +chronyc_conf=" +cyclelogs +dump +dfreq 1.0e-3 +doffset -0.01 +local stratum 5 distance 1.0 orphan +local off +makestep 10.0 3 +makestep +manual on +settime now +manual delete 0 +manual reset +manual off +onoffline +refresh +rekey +reload sources +reselect +reselectdist 1e-3 +reset sources +shutdown" + +run_test || test_fail +check_chronyd_exit || test_fail + +check_chronyc_output "^200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +Clock was .\... seconds fast. Frequency change = 0.00ppm, new frequency = 0.00ppm +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK +200 OK$" || test_fail + +server_conf=" +server 192.168.123.1 +noclientlog" + +commands=( + "add server nosuchnode.net1.clk" "^Invalid host/IP address$" + "allow nosuchnode.net1.clk" "^Could not read address$" + "allow 192.168.123.0/2 4" "^Could not read address$" + "allow 192.168.123.0/2e" "^Could not read address$" + "allow 192.168.12e" "^Could not read address$" + "allow 192.168123" "^Could not read address$" + "allow 192.168.123.2/33" "^507 Bad subnet$" + "clients" "Hostname.*519 Client logging is not active in the daemon$" + "delete 192.168.123.2" "^503 No such source$" + "minpoll 192.168.123.2 5" "^503 No such source$" + "ntpdata 192.168.123.2" "^503 No such source$" + "settime now" "^505 Facility not enabled in daemon$" + "smoothing" "^505 Facility not enabled in daemon$" + "smoothtime activate" "^505 Facility not enabled in daemon$" + "smoothtime reset" "^505 Facility not enabled in daemon$" + "sourcename 192.168.123.2" "^503 No such source$" + "trimrtc" "^513 RTC driver not running$" + "writertc" "^513 RTC driver not running$" +) + +for i in $(seq 0 $[${#commands[*]} / 2]); do + chronyc_conf=${commands[$[i * 2]]} + run_test || test_fail + check_chronyd_exit || test_fail + check_chronyc_output "${commands[$[i * 2 + 1]]}" || test_fail +done + +cmdmon_unix=0 +server_conf="server 192.168.123.1" + +chronyc_conf="dns -n +dns +n +dns -4 +dns -6 +dns -46 +timeout 200 +retries 1 +keygen +keygen 10 MD5 128 +keygen 11 MD5 40 +help +quit +nosuchcommand" + +run_test || test_fail + +check_chronyc_output "^1 (MD5|SHA1) HEX:........................................ +10 MD5 HEX:................................ +11 MD5 HEX:.................... +System clock:.*this help + *$" || test_fail + +chronyc_conf="keygen 10 NOSUCHTYPE 128 +help" +run_test || test_fail +check_chronyc_output "^Unknown hash function or cipher NOSUCHTYPE\$" || test_fail + +if check_config_h 'FEAT_SECHASH 1'; then + for hash in MD5 SHA1 SHA256 SHA384 SHA512; do + chronyc_conf="keygen 5 $hash" + run_test || test_fail + check_chronyc_output "^5 $hash HEX:........................................\$" || test_fail + done +fi + +if check_config_h 'HAVE_CMAC 1'; then + chronyc_conf="keygen 6 AES128 +keygen 7 AES256" + run_test || test_fail + check_chronyc_output "^6 AES128 HEX:................................ +7 AES256 HEX:................................................................\$" || test_fail +fi + +# Pass every fourth request +base_delay=$(cat <<-EOF | tr -d '\n' + (+ 1e-4 + (* -1 + (equal 0.1 from 2) + (equal 0.1 (min (% (sum 1) 4) 1) 1))) +EOF +) +limit=15 + +chronyc_conf="sources" +run_test || test_fail +check_chronyc_output "^506 Cannot talk to daemon$" || test_fail + +chronyc_conf="retries 3 +sources" +run_test || test_fail +check_chronyc_output "^MS.*0ns$" || test_fail + +test_pass diff --git a/test/simulation/111-knownclient b/test/simulation/111-knownclient new file mode 100755 index 0000000..92bad54 --- /dev/null +++ b/test/simulation/111-knownclient @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "reply to client configured as server" + +server_conf="server 192.168.123.2 noselect +acquisitionport 123" +client_conf="acquisitionport 123" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_port || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/112-port b/test/simulation/112-port new file mode 100755 index 0000000..2f10eed --- /dev/null +++ b/test/simulation/112-port @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "port and acquisitionport directives" + +check_config_h 'FEAT_CMDMON 1' || test_skip + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail +# This check is expected to fail +check_packet_port && test_fail + +client_conf="acquisitionport 123" +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_port || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +client_conf="" +for server_conf in \ + "port 0" \ + "acquisitionport 123 +port 0" +do + run_test || test_fail + check_chronyd_exit || test_fail + check_packet_port || test_fail + check_packet_interval || test_fail + # These checks are expected to fail + check_source_selection && test_fail + check_sync && test_fail +done + +server_conf="port 124 +acquisitionport 123" +client_server_options="port 124" +for client_conf in \ + "acquisitionport 0" \ + "acquisitionport 123" \ + "acquisitionport 124" +do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail + # This check is expected to fail + check_packet_port && test_fail +done + +test_pass diff --git a/test/simulation/113-leapsecond b/test/simulation/113-leapsecond new file mode 100755 index 0000000..394440b --- /dev/null +++ b/test/simulation/113-leapsecond @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "leap second" + +check_config_h 'FEAT_REFCLOCK 1' || test_skip + +export CLKNETSIM_START_DATE=$(TZ=UTC date -d 'Dec 30 2008 0:00:00' +'%s') + +leap=$[2 * 24 * 3600] +limit=$[4 * 24 * 3600] +client_start=$[2 * 3600] +server_conf="refclock SHM 0 dpoll 10 poll 10 +leapsectz right/UTC" +refclock_jitter=1e-9 +refclock_offset="(* -1.0 (equal 0.1 (max (sum 1.0) $leap) $leap))" + +for leapmode in system step slew; do + client_conf="leapsecmode $leapmode" + if [ $leapmode = slew ]; then + max_sync_time=$[$leap + 12] + else + max_sync_time=$[$leap] + fi + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +client_server_options="trust" +client_conf="refclock SHM 0 dpoll 10 poll 10 delay 1e-3" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +client_server_options="" +client_conf="leapsecmode system" +min_sync_time=230000 +max_sync_time=240000 + +for smoothmode in "" "leaponly"; do + server_conf="refclock SHM 0 dpoll 10 poll 10 + leapsectz right/UTC + leapsecmode slew + smoothtime 400 0.001 $smoothmode" + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/114-presend b/test/simulation/114-presend new file mode 100755 index 0000000..4fd89f8 --- /dev/null +++ b/test/simulation/114-presend @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "presend option" + +min_sync_time=136 +max_sync_time=260 +client_server_options="presend 6 maxdelay 16" +client_conf="maxdistance 10" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +base_delay=5 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/115-cmdmontime b/test/simulation/115-cmdmontime new file mode 100755 index 0000000..525062d --- /dev/null +++ b/test/simulation/115-cmdmontime @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "cmdmon timestamps" + +# The following tests need 64-bit time_t +check_config_h 'HAVE_LONG_TIME_T 1' || test_skip +check_config_h 'FEAT_CMDMON 1' || test_skip + +limit=2 +client_server_options="noselect" +client_conf="local stratum 1" +chronyc_start="1.5" +chronyc_conf="tracking" + +for year in `seq 1850 100 2300`; do + export CLKNETSIM_START_DATE=$(date -d "Jan 01 00:00:05 $year UTC" +'%s') + run_test || test_fail + check_chronyd_exit || test_fail + check_chronyc_output "^.*Ref time \(UTC\).*Jan 01 00:00:0. $year.*$" || test_fail +done + +test_pass diff --git a/test/simulation/116-minsources b/test/simulation/116-minsources new file mode 100755 index 0000000..f576423 --- /dev/null +++ b/test/simulation/116-minsources @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "minsources directive" + +client_conf="minsources 3" + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +# These check are expected to fail +check_source_selection && test_fail +check_sync && test_fail + +servers=3 + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +check_source_selection || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/117-fallbackdrift b/test/simulation/117-fallbackdrift new file mode 100755 index 0000000..21f6963 --- /dev/null +++ b/test/simulation/117-fallbackdrift @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "fallback drift" + +limit=100000 +wander=0.0 +jitter=1e-6 +time_offset=10 +freq_offset="(* 1e-4 (sine 1000))" +base_delay="(* -1.0 (equal 0.1 (min time 4250) 4250))" +client_server_options="minpoll 4 maxpoll 4" +client_conf="fallbackdrift 6 10" +max_sync_time=4500 +time_max_limit=1e0 +time_rms_limit=1e0 +freq_max_limit=2e-4 +freq_rms_limit=1e-4 + +run_test || test_fail +check_chronyd_exit || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/118-maxdelay b/test/simulation/118-maxdelay new file mode 100755 index 0000000..117b170 --- /dev/null +++ b/test/simulation/118-maxdelay @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "maxdelay options" + +max_sync_time=2000 +base_delay=1e-5 +jitter=1e-5 +wander=0.0 +freq_offset="(sum 1e-10)" +time_rms_limit=2e-4 + +client_server_options="maxpoll 6 maxdelay 3e-5 maxdelayratio 2.0 maxdelaydevratio 2.0" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +for client_server_options in "maxpoll 6 maxdelay 2e-5"; do + run_test || test_fail + check_chronyd_exit || test_fail + check_packet_interval || test_fail + check_sync && test_fail +done + +min_sync_time=10 +client_conf=" +logdir tmp +log rawmeasurements" +client_server_options="minpoll 2 maxpoll 2 maxdelayquant 0.1" + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_file_messages "20.*123\.1.* 111 111 1111" 200 500 measurements.log || test_fail +check_file_messages "20.*123\.1.* 111 111 1101" 2000 2300 measurements.log || test_fail + +test_pass diff --git a/test/simulation/119-smoothtime b/test/simulation/119-smoothtime new file mode 100755 index 0000000..7f5114c --- /dev/null +++ b/test/simulation/119-smoothtime @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "smoothtime option" + +check_config_h 'FEAT_REFCLOCK 1' || test_skip + +server_strata=2 +server_conf="smoothtime 400 0.001" +server_server_options="minpoll 8" +min_sync_time=600 +max_sync_time=800 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +limit=10000 +refclock_jitter=1e-4 +refclock_offset="(* 10.0 (equal 0.1 (max (sum 1.0) 1000) 1000))" +server_step="(* -10.0 (equal 0.1 (sum 1.0) 1))" +server_strata=1 +server_conf="refclock SHM 0 dpoll 4 poll 6 +smoothtime 2000 1 +maxjitter 10.0" +time_offset=-10 +server_server_options="" +client_server_options="minpoll 6 maxpoll 6" +client_conf="corrtimeratio 100" +min_sync_time=8000 +max_sync_time=9000 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +client_server_options="minpoll 6 maxpoll 6 xleave maxdelay 1e-1" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +client_server_options="minpoll 6 maxpoll 6" +min_sync_time=$default_min_sync_time +max_sync_time=$default_max_sync_time +time_max_limit=11 +time_rms_limit=11 +freq_max_limit=1e-2 +freq_rms_limit=2e-3 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +refclock_jitter=1e-9 +refclock_offset="(* 1e-1 (triangle 1000) (+ -1.0 (pulse 1000 10000)))" +server_step="" +server_conf="refclock SHM 0 dpoll 4 poll 6 minsamples 4 maxsamples 4 +smoothtime 1e4 1e-6" +client_server_options="minpoll 4 maxpoll 4" +time_offset=0.1 +jitter=1e-6 +wander=0.0 +min_sync_time=30 +max_sync_time=40 +time_max_limit=1e-5 +time_rms_limit=5e-6 +freq_max_limit=1e-6 +freq_rms_limit=1e-7 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/120-selectoptions b/test/simulation/120-selectoptions new file mode 100755 index 0000000..611815e --- /dev/null +++ b/test/simulation/120-selectoptions @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "source selection options" + +servers=3 +falsetickers=2 + +base_delay=0.6 +client_server_conf=" +server 192.168.123.1 +server 192.168.123.2 +server 192.168.123.3 trust" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +client_server_conf=" +server 192.168.123.1 +server 192.168.123.2 +server 192.168.123.3 prefer" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +# This check is expected to fail +check_sync && test_fail + +base_delay=1.1 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +base_delay=1e-3 +falsetickers=1 + +client_server_conf=" +server 192.168.123.1 +server 192.168.123.2 +server 192.168.123.3 require" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +client_server_conf=" +server 192.168.123.1 require +server 192.168.123.2 +server 192.168.123.3" + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +# These checks are expected to fail +check_source_selection && test_fail +check_sync && test_fail + +cat > tmp/keys <<-EOF +1 MD5 HEX:1B81CBF88D4A73F2E8CE59647F6E5C1719B6CAF5 +EOF + +server_conf="keyfile tmp/keys" +client_server_conf=" +server 192.168.123.1 key 1 +server 192.168.123.2 +server 192.168.123.3" + +for authselectmode in require prefer mix ignore; do + client_conf="keyfile tmp/keys + authselectmode $authselectmode" + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + if [ $authselectmode = ignore ]; then + check_sync || test_fail + else + check_sync && test_fail + fi +done + +test_pass diff --git a/test/simulation/121-orphan b/test/simulation/121-orphan new file mode 100755 index 0000000..7579997 --- /dev/null +++ b/test/simulation/121-orphan @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "orphan option" + +check_config_h 'FEAT_CMDMON 1' || test_skip + +server_strata=3 +server_conf="local stratum 5 orphan +server 192.168.123.1 +server 192.168.123.2 +server 192.168.123.3" +max_sync_time=900 +client_start=140 +chronyc_start=700 +chronyc_conf="tracking" +time_rms_limit=5e-4 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail +check_chronyc_output "^.*Stratum *: 7.*$" || test_fail + +test_pass diff --git a/test/simulation/122-xleave b/test/simulation/122-xleave new file mode 100755 index 0000000..c19063a --- /dev/null +++ b/test/simulation/122-xleave @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "interleaved mode" + +client_server_options="xleave" +client_conf=" +logdir tmp +log rawmeasurements" + +server_conf="noclientlog" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_file_messages "111 111 .111.* 4I [DKH] [DKH]\$" 0 0 measurements.log || test_fail +rm -f tmp/measurements.log + +server_conf="" +max_sync_time=270 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_file_messages "111 111 1111.* 4B [DKH] [DKH]\$" 2 2 measurements.log || test_fail +check_file_messages "111 111 1111.* 4I [DKH] [DKH]\$" 30 200 measurements.log || test_fail +check_file_messages "111 111 0111.* 4I [DKH] [DKH]\$" 1 1 measurements.log || test_fail +rm -f tmp/measurements.log + +clients=2 +peers=2 +max_sync_time=500 +base_delay="(+ 1e-4 (* -1 (equal 0.1 from 2) (equal 0.1 to 1)))" + +client_lpeer_options="xleave minpoll 5 maxpoll 5" +client_rpeer_options="minpoll 5 maxpoll 5" + +run_test || test_fail +check_chronyd_exit || test_fail +# These checks are expected to fail +check_source_selection && test_fail +check_sync && test_fail + +rm -f tmp/measurements.log + +for rpoll in 4 5 6; do + client_rpeer_options="xleave minpoll $rpoll maxpoll $rpoll" + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + + if [ $rpoll -le 5 ]; then + check_file_messages "111 111 1111.* 1B [DKH] [DKH]\$" 0 0 measurements.log || test_fail + check_file_messages "111 111 1111.* 1I [DKH] [DKH]\$" 200 310 measurements.log || test_fail + else + check_file_messages "111 111 1111.* 1B [DKH] [DKH]\$" 125 135 measurements.log || test_fail + check_file_messages "111 111 1111.* 1I [DKH] [DKH]\$" 20 30 measurements.log || test_fail + fi + rm -f tmp/measurements.log +done + +if check_config_h 'FEAT_CMDMON 1'; then + # test client timestamp selection and server timestamp correction + base_delay="(+ 1.25e-6 (* -1 (equal 0.1 from 5)))" + jitter=1e-9 + wander=1e-12 + client_lpeer_options="xleave minpoll 5 maxpoll 5 noselect" + client_rpeer_options="xleave minpoll 5 maxpoll 5 noselect" + chronyc_conf="doffset -0.1" + chronyc_start=7200 + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync && test_fail + + check_file_messages "\.2 N 2 111 111 .... 5 5 .\... ..\....e-.. 2\....e-06" \ + 290 310 measurements.log || test_fail + check_file_messages "\.2 N 2 111 111 .... 5 5 .\... ..\....e-.. .\....e-0[0123]" \ + 0 0 measurements.log || test_fail + rm -f tmp/measurements.log +fi + +test_pass diff --git a/test/simulation/123-mindelay b/test/simulation/123-mindelay new file mode 100755 index 0000000..89a9f33 --- /dev/null +++ b/test/simulation/123-mindelay @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "mindelay and asymmetry options" + +jitter_asymmetry=0.499 +time_rms_limit=1e-6 +time_freq_limit=1e-9 +wander=1e-12 + +for client_server_options in "mindelay 2e-4 asymmetry 0.499"; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_packet_interval || test_fail + check_sync || test_fail +done + +for client_server_options in "mindelay 1e-4 asymmetry 0.499" "mindelay 2e-4 asymmetry 0.0"; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync && test_fail +done + +test_pass diff --git a/test/simulation/124-tai b/test/simulation/124-tai new file mode 100755 index 0000000..97064f7 --- /dev/null +++ b/test/simulation/124-tai @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "tai option" + +check_config_h 'FEAT_REFCLOCK 1' || test_skip + +export CLKNETSIM_START_DATE=$(TZ=UTC date -d 'Dec 31 2008 23:50:00' +'%s') + +leap=$[10 * 60] +limit=$[20 * 60] +min_sync_time=2 +max_sync_time=15 +refclock_jitter=1e-6 +servers=0 + +refclock_offset="(+ -34 (equal 0.1 (max (sum 1.0) $leap) $leap))" +client_conf=" +refclock SHM 0 dpoll 0 poll 0 tai +leapsectz right/UTC +leapsecmode ignore +maxchange 1e-3 1 0" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +export CLKNETSIM_START_DATE=$(TZ=UTC date -d 'Jan 01 2009 00:10:00' +'%s') + +time_offset=-1000 +refclock_offset="(+ -34)" +client_conf=" +refclock SHM 0 dpoll 0 poll 0 tai +leapsectz right/UTC +makestep 1 1 +maxchange 1e-3 1 0" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/125-packetloss b/test/simulation/125-packetloss new file mode 100755 index 0000000..505e4fa --- /dev/null +++ b/test/simulation/125-packetloss @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "packet loss" + +# Drop 33% of packets by default and 100% on the 3->1 path +base_delay=$(cat <<-EOF | tr -d '\n' + (+ 1e-4 + (* -1 (equal 0.33 (uniform) 1.0)) + (* -1 (equal 0.1 from 3) (equal 0.1 to 1))) +EOF +) +clients=2 +peers=2 +jitter=1e-5 +limit=20000 +max_sync_time=10000 + +for options in "maxpoll 8" "maxpoll 8 xleave"; do + client_server_options=$options + client_peer_options=$options + + run_test || test_fail + check_chronyd_exit || test_fail + check_sync || test_fail +done + +test_pass diff --git a/test/simulation/126-burst b/test/simulation/126-burst new file mode 100755 index 0000000..1cb6f9c --- /dev/null +++ b/test/simulation/126-burst @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "burst option" + +# Pass every fourth packet on the 2->1 path +base_delay=$(cat <<-EOF | tr -d '\n' + (+ 1e-4 + (* -1 + (equal 0.1 from 2) + (equal 0.1 to 1) + (equal 0.1 (min (% (sum 1) 4) 1) 1))) +EOF +) + +client_server_options="burst polltarget 1" +min_sync_time=700 +max_sync_time=730 +client_max_min_out_interval=2.2 +client_min_mean_out_interval=150.0 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +# Add a significant delay to 70% of packets on the 2->1 path after 6th packet +base_delay=$(cat <<-EOF | tr -d '\n' + (+ 1e-4 + (* 0.15 + (equal 0.1 from 2) + (equal 0.1 to 1) + (equal 0.1 (min (sum 1) 7) 7) + (equal 0.7 (uniform) 0.0))) +EOF +) + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail + +test_pass diff --git a/test/simulation/127-filter b/test/simulation/127-filter new file mode 100755 index 0000000..739dd91 --- /dev/null +++ b/test/simulation/127-filter @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "filter option" + +client_server_options="minpoll 4 maxpoll 4 filter 15 maxdelay 3.5e-4" +min_sync_time=710 +max_sync_time=720 +client_max_min_out_interval=16.1 +client_min_mean_out_interval=15.9 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +base_delay="(+ 1e-4 (* -1 (equal 0.3 (uniform) 0.0)))" +client_server_options="minpoll 4 maxpoll 4 filter 3" +min_sync_time=130 + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +limit=10 +client_server_options="minpoll -6 maxpoll -6 filter 1" + +base_delay=1e-4 + +run_test || test_fail +check_chronyd_exit || test_fail +check_file_messages " 2 1 " 590 640 log.packets || test_fail + +base_delay=2e-2 + +run_test || test_fail +check_chronyd_exit || test_fail +check_file_messages " 2 1 " 9 10 log.packets || test_fail + +test_pass diff --git a/test/simulation/128-nocontrol b/test/simulation/128-nocontrol new file mode 100755 index 0000000..3f0d18d --- /dev/null +++ b/test/simulation/128-nocontrol @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "-x option" + +check_config_h 'FEAT_CMDMON 1' || test_skip + +wander=0.0 +time_offset=0.0 +freq_offset=0.0 +time_max_limit=1e-6 +freq_max_limit=1e-9 +min_sync_time=0 +max_sync_time=0 +client_chronyd_options="-x" +chronyc_start=300 +chronyc_conf="tracking" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail +check_chronyc_output "^.*Stratum *: 2.*$" || test_fail + +test_pass diff --git a/test/simulation/129-reload b/test/simulation/129-reload new file mode 100755 index 0000000..56bc3da --- /dev/null +++ b/test/simulation/129-reload @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "-r option" + +wander=0.0 +limit=100 +min_sync_time=100 +max_sync_time=104 +client_chronyd_options="-r" +client_conf="dumpdir tmp +maxupdateskew 10000" + +run_test || test_fail +check_chronyd_exit || test_fail + +check_log_messages "Loaded dump file" 0 0 || test_fail +check_file_messages "." 6 6 192.168.123.1.dat || test_fail + +client_start=$limit +limit=1000 + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_log_messages "Loaded dump file" 1 1 || test_fail +check_file_messages "." 10 30 192.168.123.1.dat || test_fail + +rm -f tmp/*.dat + +client_start=0 +limit=200 +jitter=1e-6 +client_conf="dumpdir tmp +maxupdateskew 1e-6 +maxslewrate 1e-6" + +run_test || test_fail +check_chronyd_exit || test_fail + +check_log_messages "Loaded dump file" 0 0 || test_fail +check_file_messages "." 8 8 192.168.123.1.dat || test_fail +cp tmp/192.168.123.1.dat tmp/backup.dat + +client_start=$limit +limit=1000 +min_sync_time=201 +max_sync_time=203 +client_server_options="offline" +client_conf="dumpdir tmp" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_log_messages "Loaded dump file" 1 1 || test_fail +check_file_messages "." 8 8 192.168.123.1.dat || test_fail + +cp -f tmp/backup.dat tmp/192.168.123.1.dat + +client_server_options="key 1" + +run_test || test_fail +check_chronyd_exit || test_fail +check_sync && test_fail + +check_log_messages "Could not load dump file" 1 1 || test_fail +check_log_messages "Loaded dump file" 0 0 || test_fail + +client_server_options="" + +if check_config_h 'FEAT_REFCLOCK 1'; then + refclock_jitter=1e-6 + servers=0 + client_start=0 + limit=40 + min_sync_time=56 + max_sync_time=58 + client_chronyd_options="-r" + client_conf="dumpdir tmp + refclock SHM 0" + + run_test || test_fail + check_chronyd_exit || test_fail + + check_log_messages "Loaded dump file" 0 0 || test_fail + check_file_messages "." 6 6 refid:53484d30.dat || test_fail + + client_start=$limit + limit=300 + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + + check_log_messages "Loaded dump file" 1 1 || test_fail + check_file_messages "." 6 23 refid:53484d30.dat || test_fail + + rm -f tmp/*.dat +fi + +test_pass diff --git a/test/simulation/130-quit b/test/simulation/130-quit new file mode 100755 index 0000000..da2b8cf --- /dev/null +++ b/test/simulation/130-quit @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "-q/-Q option" + +wander=0.0 +freq_offset=0.0 +min_sync_time=5 +max_sync_time=10 +client_chronyd_options="-q" +client_server_options="iburst" + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +min_sync_time=1 +max_sync_time=1 +client_server_options="iburst maxsamples 1" + +run_test || test_fail +check_chronyd_exit || test_fail +check_sync || test_fail + +client_chronyd_options="-Q" +run_test || test_fail +check_sync && test_fail + +test_pass diff --git a/test/simulation/131-maxchange b/test/simulation/131-maxchange new file mode 100755 index 0000000..59cc0c1 --- /dev/null +++ b/test/simulation/131-maxchange @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "maxchange directive" + +time_offset=2 +max_sync_time=5000 +client_conf="maxchange 0.1 1 3" +client_step="(* $step (equal 0.1 (sum 1.0) 300))" + +run_test || test_fail +check_chronyd_exit && test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync && test_fail +check_log_messages "seconds exceeds.*ignored" 3 3 || test_fail +check_log_messages "seconds exceeds.*exiting" 1 1 || test_fail + +test_pass diff --git a/test/simulation/132-logchange b/test/simulation/132-logchange new file mode 100755 index 0000000..59ddf7c --- /dev/null +++ b/test/simulation/132-logchange @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "logchange directive" + +time_offset=2 +min_sync_time=590 +max_sync_time=700 +client_server_options="maxsamples 6" +client_conf="logchange 0.1" +client_step="(* $step (equal 0.1 (sum 1.0) 300))" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail +check_log_messages "clock wrong by" 4 8 || test_fail + +test_pass diff --git a/test/simulation/133-hwtimestamp b/test/simulation/133-hwtimestamp new file mode 100755 index 0000000..d3cce6d --- /dev/null +++ b/test/simulation/133-hwtimestamp @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "hwtimestamp directive" + +check_config_h 'HAVE_LINUX_TIMESTAMPING 1' || test_skip + +export CLKNETSIM_TIMESTAMPING=2 +export CLKNETSIM_PHC_DELAY=1e-6 +export CLKNETSIM_PHC_JITTER=1e-7 +export CLKNETSIM_PHC_JITTER_ASYM=0.4 + +refclock_jitter=1e-8 +refclock_offset=10.0 +min_sync_time=4 +max_sync_time=20 +time_rms_limit=1e-7 +freq_rms_limit=3e-8 +jitter=1e-8 +freq_offset=1e-5 +limit=200 +server_conf=" +clockprecision 1e-9 +hwtimestamp eth0" +client_server_options="minpoll 0 maxpoll 0 xleave" +client_chronyd_options="-d" + +for client_conf in \ + "hwtimestamp eth0 nocrossts + clockprecision 1e-9" \ + "hwtimestamp eth0 + clockprecision 1e-9 + acquisitionport 123"; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + + if check_config_h 'FEAT_DEBUG 1'; then + check_log_messages "Accepted reading" 0 2 || test_fail + check_log_messages "Combined .* readings" 190 220 || test_fail + check_log_messages "HW clock samples" 190 200 || test_fail + check_log_messages "HW clock reset" 0 0 || test_fail + check_log_messages "Missing TX timestamp" 1 1 || test_fail + check_log_messages "Received message.*tss=KH" 195 200 || test_fail + check_log_messages "Received error.*message.*tss=KH" 195 200 || test_fail + check_log_messages "Updated RX timestamp.*tss=1" 1 1 || test_fail + check_log_messages "Updated RX timestamp.*tss=2" 195 200 || test_fail + if echo "$client_conf" | grep -q nocrossts; then + check_log_messages "update_tx_timestamp.*Updated" 180 200 || test_fail + check_log_messages "update_tx_timestamp.*Unacceptable" 0 10 || test_fail + else + check_log_messages "update_tx_timestamp.*Updated" 50 140 || test_fail + check_log_messages "update_tx_timestamp.*Unacceptable" 50 140 || test_fail + fi + fi +done + +test_pass diff --git a/test/simulation/134-log b/test/simulation/134-log new file mode 100755 index 0000000..ab1ced2 --- /dev/null +++ b/test/simulation/134-log @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "log directive" + +check_config_h 'FEAT_PHC 1' || test_skip + +refclock_jitter=$jitter +client_server_options="maxpoll 6" +client_conf="refclock PHC /dev/ptp0 dpoll 4 poll 6 noselect +logbanner 10 +logdir tmp +log tracking rawmeasurements measurements selection statistics rtc refclocks tempcomp +tempcomp tmp/tempcomp 64 0.0 0.0 0.0 0.0" + +echo 0.0 > tmp/tempcomp + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_file_messages "=============" 31 33 \ + tracking.log measurements.log tempcomp.log || test_fail +check_file_messages "20.*192\.168\.123\.1" 150 160 \ + tracking.log measurements.log statistics.log || test_fail +check_file_messages "20.*PHC0 * N " 300 320 selection.log || test_fail +check_file_messages "20.*192\.168\.123\.1 *[M*]" 300 320 selection.log || test_fail +check_file_messages "20.*PHC0" 150 160 statistics.log || test_fail +check_file_messages "20.*PHC0" 750 800 refclocks.log || test_fail +check_file_messages "20.* 0\.0000" 150 160 tempcomp.log || test_fail + +test_pass diff --git a/test/simulation/135-ratelimit b/test/simulation/135-ratelimit new file mode 100755 index 0000000..86c435d --- /dev/null +++ b/test/simulation/135-ratelimit @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "ratelimit directive" + +server_conf="ratelimit interval 6 burst 2 leak 4" +client_server_options="minpoll 3 maxpoll 3" +min_sync_time=16 + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_file_messages " 2 1 " 1200 1300 log.packets || test_fail +check_file_messages " 1 2 " 180 220 log.packets || test_fail + +test_pass diff --git a/test/simulation/136-broadcast b/test/simulation/136-broadcast new file mode 100755 index 0000000..1488c53 --- /dev/null +++ b/test/simulation/136-broadcast @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "broadcast directive" + +server_conf="broadcast 64 192.168.123.255" +client_server_options="offline" + +run_test || test_fail +check_chronyd_exit || test_fail +check_packet_interval && test_fail + +check_file_messages " 1 2 " 150 160 log.packets || test_fail + +test_pass diff --git a/test/simulation/137-pool b/test/simulation/137-pool new file mode 100755 index 0000000..de8d77d --- /dev/null +++ b/test/simulation/137-pool @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "pool directive" + +limit=500 +client_conf="logdir tmp +log measurements" + +servers=3 +client_server_conf="pool nodes-1-2-3.net1.clk" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_file_messages "20.*192.168.123.1" 5 10 measurements.log || test_fail +check_file_messages "20.*192.168.123.2" 5 10 measurements.log || test_fail +check_file_messages "20.*192.168.123.3" 5 10 measurements.log || test_fail +rm -f tmp/measurements.log + +servers=6 +client_server_conf="pool nodes-1-2-3-4-5-6.net1.clk minpoll 6 maxpoll 6" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_file_messages "20.*192.168.123.*" 30 35 measurements.log || test_fail +rm -f tmp/measurements.log + +servers=6 +client_server_conf="pool nodes-1-2-3-4-5-6.net1.clk maxsources 2 minpoll 6 maxpoll 6" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_file_messages "20.*192.168.123.*" 15 17 measurements.log || test_fail +rm -f tmp/measurements.log + +test_pass diff --git a/test/simulation/138-syncloop b/test/simulation/138-syncloop new file mode 100755 index 0000000..2d3999e --- /dev/null +++ b/test/simulation/138-syncloop @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "loop prevention" + +mkdir tmp/logdir1 tmp/logdir2 + +server_conf=" +server 192.168.123.1 +server 192.168.123.2 +logdir tmp/logdir1 +log measurements" +client_server_conf=" +server 192.168.123.1 +server 192.168.123.2 +logdir tmp/logdir2 +log measurements +allow" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_file_messages "20.*123\.1.* 111 111 1110" 30 200 logdir1/measurements.log || test_fail +check_file_messages "20.*123\.2.* 111 111 1110" 30 200 logdir1/measurements.log || test_fail +check_file_messages "20.*123\...* 111 111 1111" 0 0 logdir1/measurements.log || test_fail +check_file_messages "20.*123\.1.* 111 111 1111" 30 200 logdir2/measurements.log || test_fail +check_file_messages "20.*123\.1.* 111 111 1110" 0 0 logdir2/measurements.log || test_fail +check_file_messages "20.*123\.2.* 111 111 1110" 30 200 logdir2/measurements.log || test_fail +check_file_messages "20.*123\.2.* 111 111 1111" 0 0 logdir1/measurements.log || test_fail + +test_pass diff --git a/test/simulation/139-nts b/test/simulation/139-nts new file mode 100755 index 0000000..6a2112d --- /dev/null +++ b/test/simulation/139-nts @@ -0,0 +1,312 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "NTP authentication with NTS" + +check_config_h 'FEAT_NTS 1' || test_skip +certtool --help &> /dev/null || test_skip + +export CLKNETSIM_START_DATE=$(date -d 'Jan 1 00:00:00 UTC 2010' +'%s') + +for i in 1 2; do + cat > tmp/cert$i.cfg <<-EOF + cn = "node$i.net1.clk" + dns_name = "node$i.net1.clk" + ip_address = "192.168.123.$i" + serial = 001 + activation_date = "2010-01-01 00:00:00 UTC" + expiration_date = "2010-01-02 00:00:00 UTC" + signing_key + encryption_key + EOF + + certtool --generate-privkey --key-type=ed25519 --outfile tmp/server$i.key &> \ + tmp/log.certtool$i + certtool --generate-self-signed --load-privkey tmp/server$i.key \ + --template tmp/cert$i.cfg --outfile tmp/server$i.crt &>> tmp/log.certtool$i +done + +max_sync_time=400 +dns=1 +server_conf=" +ntsserverkey tmp/server1.key +ntsservercert tmp/server1.crt +ntsprocesses 0 +ntsrotate 66 +ntsdumpdir tmp +" +client_server_options="minpoll 6 maxpoll 6 nts" +client_conf=" +nosystemcert +ntstrustedcerts /dev/null +ntstrustedcerts tmp/server1.crt +ntstrustedcerts /dev/null +logdir tmp +log rawmeasurements" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_file_messages "20.*123\.1.* 111 111 1111" 75 80 measurements.log || test_fail +check_file_messages "20.*123\.1.* 111 001 0000" 37 39 measurements.log || test_fail +check_file_messages " 2 1 .* 4460 " 260 300 log.packets || test_fail +check_file_messages "." 6 6 ntskeys || test_fail +rm -f tmp/measurements.log + +client_conf+=" +ntsrefresh 120 +ntsdumpdir tmp" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_file_messages "20.*123\.1.* 111 111 1111" 99 103 measurements.log || test_fail +check_file_messages "20.*123\.1.* 111 001 0000" 0 0 measurements.log || test_fail +check_file_messages " 2 1 .* 4460 " 350 390 log.packets || test_fail +check_file_messages "." 6 6 ntskeys || test_fail +check_file_messages "." 12 13 192.168.123.1.nts || test_fail +rm -f tmp/measurements.log + +export CLKNETSIM_START_DATE=$(date -d 'Jan 1 00:00:00 UTC 2010 + 40000 sec' +'%s') + +server_conf+=" +ntsrotate 100000" +client_conf+=" +ntsrefresh 39500" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_file_messages "20.*123\.1.* 111 111 1111" 150 160 measurements.log || test_fail +check_file_messages "20.*123\.1.* 111 001 0000" 0 0 measurements.log || test_fail +check_file_messages " 2 1 .* 4460 " 6 10 log.packets || test_fail +check_file_messages "^9\.......e+03 2 1 .* 4460 " 6 10 log.packets || test_fail +check_file_messages "." 6 6 ntskeys || test_fail +check_file_messages "." 12 13 192.168.123.1.nts || test_fail +rm -f tmp/measurements.log + +client_conf=" +nosystemcert" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection && test_fail +check_sync && test_fail + +check_file_messages " 2 1 .* 123 " 0 0 log.packets || test_fail +check_file_messages " 2 1 .* 4460 " 10 20 log.packets || test_fail + +export CLKNETSIM_START_DATE=$(date -d 'Jan 2 00:00:01 UTC 2010' +'%s') + +client_conf=" +nosystemcert +ntstrustedcerts tmp/server1.crt" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection && test_fail +check_sync && test_fail + +check_file_messages " 2 1 .* 123 " 0 0 log.packets || test_fail +check_file_messages " 2 1 .* 4460 " 10 20 log.packets || test_fail +check_log_messages "expired certificate" 4 4 || test_fail + +client_conf+=" +nocerttimecheck 1" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +export CLKNETSIM_START_DATE=$(date -d 'Jan 1 00:00:00 UTC 2010' +'%s') + +client_conf=" +nosystemcert +ntstrustedcerts tmp/server1.crt +ntsrefresh 500" + +for dns in 1 0; do + server_conf=" + ntsserverkey tmp/server1.key + ntsservercert tmp/server1.crt + ntsprocesses 0 + ntsrotate 0 + ntsdumpdir tmp" + + if [ $dns != 0 ]; then + server_conf+=" + ntsntpserver node2.net1.clk" + client_server_conf="server node1.net1.clk $client_server_options" + else + server_conf+=" + ntsntpserver 192.168.123.2" + client_server_conf="server 192.168.123.1 $client_server_options" + fi + + servers=1 + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection && test_fail + check_sync && test_fail + + check_file_messages " 2 1 .* 4460 " 50 100 log.packets || test_fail + check_file_messages " 2 2 .* 4460 " 0 0 log.packets || test_fail + check_log_messages "Source 192.168.123.1 changed to 192.168.123.2" 6 8 || test_fail + check_log_messages "Source 192.168.123.2 replaced with 192.168.123.1" 6 8 || test_fail + + servers=2 + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + + check_file_messages " 3 1 .* 4460 " 100 150 log.packets || test_fail + check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail + check_log_messages "Source 192.168.123.1 changed to 192.168.123.2" 1 1 || test_fail + check_log_messages "Source 192.168.123.2 replaced with 192.168.123.1" 0 0 || test_fail + + server_conf+=" + ntsratelimit interval 12 burst 1 leak 4" + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection && test_fail + + check_file_messages " 3 1 .* 4460 1 0 2" 25 50 log.packets || test_fail + check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail + check_log_messages "Source 192.168.123.1 changed to 192.168.123.2" 2 6 || test_fail + check_log_messages "Source 192.168.123.2 replaced with 192.168.123.1" 1 6 || test_fail +done + +servers=2 +server_conf=" +ntsserverkey tmp/server1.key +ntsservercert tmp/server1.crt +ntsprocesses 0 +ntsrotate 0 +ntsntpserver node2.net1.clk +port 11123 +ntsdumpdir tmp" +client_conf=" +nosystemcert +ntstrustedcerts tmp/server1.crt +ntsdumpdir tmp" +client_server_conf="server 192.168.123.1 $client_server_options" + +rm -f tmp/*.nts + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_log_messages "Could not change" 0 0 || test_fail +check_file_messages " 3 1 .* 4460 1 0 2" 1 1 log.packets || test_fail +check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail + +for dns in 1 0; do + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + + check_log_messages "Could not change" 0 0 || test_fail + check_file_messages " 3 1 .* 4460 1 0 2" 0 0 log.packets || test_fail + check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail +done + +server_conf=" +ntsserverkey tmp/server1.key +ntsservercert tmp/server1.crt +ntsprocesses 0 +ntsrotate 0 +ntsdumpdir tmp" + +head -n 8 tmp/192.168.123.1.nts > tmp/192.168.123.1.nts_ +mv tmp/192.168.123.1.nts_ tmp/192.168.123.1.nts + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_log_messages "Could not change" 0 0 || test_fail +check_file_messages " 3 1 .* 4460 1 0 2" 1 1 log.packets || test_fail +check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail +check_file_messages " 3 1 .* 11123 " 0 0 log.packets || test_fail +check_file_messages " 3 2 .* 123 " 0 0 log.packets || test_fail +check_file_messages " 3 2 .* 11123 " 3 3 log.packets || test_fail + +dns=1 +server_conf=" +ntsserverkey tmp/server1.key +ntsservercert tmp/server1.crt +ntsserverkey tmp/server2.key +ntsservercert tmp/server2.crt +ntsprocesses 0" +client_conf=" +nosystemcert +ntstrustedcerts tmp/server1.crt +ntstrustedcerts tmp/server2.crt +minsources 2" +client_server_conf="" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +client_conf=" +nosystemcert +ntstrustedcerts tmp/server1.crt +ntstrustedcerts 1 tmp/server1.crt +ntstrustedcerts 2 tmp/server2.crt +ntstrustedcerts 3 tmp/server2.crt" +client_server_conf=" +server node1.net1.clk $client_server_options certset 0 +server node2.net1.clk $client_server_options certset 2" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_file_messages " 3 1 .* 123 " 100 200 log.packets || test_fail +check_file_messages " 3 2 .* 123 " 100 200 log.packets || test_fail + +client_server_conf=" +server node1.net1.clk $client_server_options certset 2 +server node2.net1.clk $client_server_options" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection && test_fail +check_sync && test_fail + +check_file_messages " 3 1 .* 123 " 0 0 log.packets || test_fail +check_file_messages " 3 2 .* 123 " 0 0 log.packets || test_fail + +client_conf=" +nosystemcert +ntstrustedcerts tmp/nosuch.crt +ntstrustedcerts 2 tmp/nosuch.crt" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection && test_fail +check_sync && test_fail + +check_file_messages " 3 1 .* 123 " 0 0 log.packets || test_fail +check_file_messages " 3 2 .* 123 " 0 0 log.packets || test_fail + +test_pass diff --git a/test/simulation/140-noclientlog b/test/simulation/140-noclientlog new file mode 100755 index 0000000..502398f --- /dev/null +++ b/test/simulation/140-noclientlog @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "noclientlog option" + +server_conf="noclientlog" +client_server_options="xleave" +client_conf=" +logdir tmp +log rawmeasurements" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +check_file_messages "111 111 1111.* 4B " 30 200 measurements.log || test_fail +check_file_messages "111 111 1111.* 4I " 0 0 measurements.log || test_fail + +test_pass diff --git a/test/simulation/141-copy b/test/simulation/141-copy new file mode 100755 index 0000000..80e56bc --- /dev/null +++ b/test/simulation/141-copy @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "copy option" + +check_config_h 'FEAT_CMDMON 1' || test_skip + +client_server_options="copy" +chronyc_conf="tracking" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail +check_chronyc_output "^Reference ID *: 7F7F0101 \(192\.168\.123\.1\) +Stratum *: 1" || test_fail + +test_pass diff --git a/test/simulation/142-ptpport b/test/simulation/142-ptpport new file mode 100755 index 0000000..060932c --- /dev/null +++ b/test/simulation/142-ptpport @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "PTP port" + +# Block communication between 3 and 1 +base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))" + +cat > tmp/peer.keys <<-EOF +1 MD5 1234567890 +EOF + +clients=2 +peers=2 +max_sync_time=420 + +server_conf=" +ptpport 319" +client_conf=" +ptpport 319 +authselectmode ignore +keyfile tmp/peer.keys" +client_server_options="minpoll 6 maxpoll 6 port 319" +client_peer_options="minpoll 6 maxpoll 6 port 319 key 1" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_file_messages " 2 1 .* 319 319 1 96 " 150 160 \ + log.packets || test_fail +check_file_messages " 1 2 .* 319 319 1 96 " 150 160 \ + log.packets || test_fail +check_file_messages " 2 3 .* 319 319 1 116 " 150 160 \ + log.packets || test_fail +check_file_messages " 3 2 .* 319 319 1 116 " 150 160 \ + log.packets || test_fail + +test_pass diff --git a/test/simulation/143-manual b/test/simulation/143-manual new file mode 100755 index 0000000..618cee6 --- /dev/null +++ b/test/simulation/143-manual @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +. ./test.common + +export TZ=UTC + +test_start "manual input" + +check_config_h 'FEAT_CMDMON 1' || test_skip + +limit=$[12 * 3600] +client_server_conf=" " +client_conf="manual" +chronyc_conf="timeout 4000000 +settime 1:00:00 +settime 2:00:00 +settime 3:00:00 +settime 4:00:00 +manual delete 2 +settime 6:00:00 +manual list +settime 8:00:00 +manual reset +settime 10:00:00 +manual list" +chronyc_start=1800 +base_delay=1800 +jitter=1e-6 + +time_max_limit=4e-3 +freq_max_limit=4e-3 +time_rms_limit=2e-3 +freq_rms_limit=2e-5 +min_sync_time=7204 +max_sync_time=7206 + +run_test || test_fail +check_chronyd_exit || test_fail +check_sync || test_fail + +check_chronyc_output "^200 OK +Clock was 0\.4. seconds fast\. Frequency change = 0\.00ppm, new frequency = 0\.00ppm +200 OK +Clock was 0\.3. seconds fast\. Frequency change = (99|100)\...ppm, new frequency = (99|100)\...ppm +200 OK +Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.[01].ppm, new frequency = (99|100)\...ppm +200 OK +Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.[01].ppm, new frequency = (99|100)\...ppm +200 OK +200 OK +Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.[012].ppm, new frequency = (99|100)\...ppm +210 n_samples = 4 +# Date Time\(UTC\) Slewed Original Residual +======================================================= + 0 2010-01-01 (00:59:59|01:00:00) [- ]0\.00 0\.46 [- ]0\.00 + 1 2010-01-01 (01:59:59|02:00:00) [- ]0\.00 0\.36 [- ]0\.00 + 2 2010-01-01 (03:59:59|04:00:00) [- ]0\.00 [- ]0\.00 [- ]0\.00 + 3 2010-01-01 (05:59:59|06:00:00) [- ]0\.00 [- ]0\.00 [- ]0\.00 +200 OK +Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.[012].ppm, new frequency = (99|100)\...ppm +200 OK +200 OK +Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.00ppm, new frequency = (99|100)\...ppm +210 n_samples = 1 +# Date Time\(UTC\) Slewed Original Residual +======================================================= + 0 2010-01-01 (09:59:59|10:00:00) [- ]0\.00 [- ]0\.00 [- ]0\.00$" \ + || test_fail + +test_pass diff --git a/test/simulation/144-exp1 b/test/simulation/144-exp1 new file mode 100755 index 0000000..4a2042d --- /dev/null +++ b/test/simulation/144-exp1 @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "experimental extension field" + +check_config_h 'FEAT_CMDMON 1' || test_skip + +primary_time_offset=0.1 +server_strata=4 +min_sync_time=2000 +max_sync_time=2300 +chronyc_conf="doffset 0.1" +chronyc_options="-h /clknetsim/unix/1:1" +chronyc_start=2000 + +for options in "extfield F323" "xleave extfield F323"; do + client_server_options="minpoll 6 maxpoll 6 $options" + server_server_options="$client_server_options" + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail +done + +server_server_options="" +server_strata=1 +clients=4 +peers=4 +max_sync_time=2400 +# chain of peers and one enabled chronyc +base_delay=$(cat <<-EOF | tr -d '\n' + (+ 1e-4 -1 + (equal 0.1 from (+ to 1)) + (equal 0.1 from (+ to -1)) + (equal 0.1 from 6) + (equal 0.1 to 6)) +EOF +) + +for lpoll in 5 6 7; do + for options in "minsamples 16 extfield F323" "minsamples 16 xleave extfield F323"; do + client_lpeer_options="minpoll $lpoll maxpoll $lpoll $options" + client_rpeer_options="minpoll 6 maxpoll 6 $options" + client_server_options="$client_rpeer_options" + + run_test || test_fail + check_chronyd_exit || test_fail + check_source_selection || test_fail + check_sync || test_fail + done +done + +test_pass diff --git a/test/simulation/201-freqaccumulation b/test/simulation/201-freqaccumulation new file mode 100755 index 0000000..6f14246 --- /dev/null +++ b/test/simulation/201-freqaccumulation @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +. ./test.common + +# Test fix in commit 60d0fa299307076143da94d36deb7b908fa9bdb7 + +test_start "frequency accumulation" + +time_offset=100.0 +jitter=1e-6 +base_delay=1e-6 +wander=0.0 + +limit=180 +time_max_limit=1e-5 +freq_max_limit=1e-7 +time_rms_limit=1e-5 +freq_rms_limit=1e-7 +min_sync_time=120 +max_sync_time=140 + +client_server_options="minpoll 6 maxpoll 6" +client_conf="driftfile tmp/drift +makestep 1 1" + +for freq_offset in -5e-2 -5e-4 5e-4 5e-2; do + for drift in -1e+4 -1e+2 1e+2 1e+4; do + echo "$drift 100000" > tmp/drift + run_test || test_fail + check_chronyd_exit || test_fail + check_sync || test_fail + done +done + +test_pass diff --git a/test/simulation/202-prefer b/test/simulation/202-prefer new file mode 100755 index 0000000..207c800 --- /dev/null +++ b/test/simulation/202-prefer @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +. ./test.common + +# Test fix in commit 4253075a97141edfa62043ab71bd0673587e6629 + +test_start "prefer option" + +servers=3 +client_server_conf=" +server 192.168.123.1 +server 192.168.123.2 +server 192.168.123.3 prefer" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_packet_interval || test_fail +check_sync || test_fail + +test_pass diff --git a/test/simulation/README b/test/simulation/README new file mode 100644 index 0000000..e174500 --- /dev/null +++ b/test/simulation/README @@ -0,0 +1,11 @@ +This is a collection of simulation tests using the clknetsim simulator +(supported on Linux only). + +https://github.com/mlichvar/clknetsim + +The CLKNETSIM_PATH environment variable should point to the directory where +clknetsim was downloaded and compiled. If the variable is not set, the tests +will look for clknetsim in ./clknetsim in the current directory. + +The tests are written in bash and they can be run directly. The ./run script +runs all tests. diff --git a/test/simulation/run b/test/simulation/run new file mode 100755 index 0000000..0954438 --- /dev/null +++ b/test/simulation/run @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +print_help() { + echo "$1 [-a] [-i ITERATIONS] [-m MAXFAILS] [-s SEED] [TEST]..." +} + +run_test() { + local result name=$1 seed=$2 + + CLKNETSIM_RANDOM_SEED=$seed ./$name + result=$? + + if [ $result -ne 0 -a $result -ne 9 ]; then + if [ $abort_on_fail -ne 0 ]; then + echo 1>&2 + echo Failed with random seed $seed 1>&2 + exit 1 + fi + failed_seeds=(${failed_seeds[@]} $seed) + fi + + return $result +} + +abort_on_fail=0 +iterations=1 +max_fails=0 +random_seed=${CLKNETSIM_RANDOM_SEED:-$RANDOM} + +while getopts ":ai:m:s:" opt; do + case $opt in + a) abort_on_fail=1;; + i) iterations=$OPTARG;; + m) max_fails=$OPTARG;; + s) random_seed=$OPTARG;; + *) print_help "$0"; exit 3;; + esac +done + +shift $[$OPTIND - 1] + +passed=() failed=() skipped=() failed_seeds=() + +[ $# -gt 0 ] && tests=($@) || tests=([0-9]*-*[^_]) + +for test in "${tests[@]}"; do + if [ $iterations -gt 1 ]; then + printf "%-30s" "$test" + fails=0 + for i in $(seq 1 $iterations); do + run_test $test $[$random_seed + $i - 1] > /dev/null + case $? in + 0) echo -n ".";; + 9) break;; + *) echo -n "x"; fails=$[$fails + 1];; + esac + done + if [ $i -lt $iterations ]; then + printf "%${iterations}s" "" + echo " SKIP" + result=9 + elif [ $fails -gt $max_fails ]; then + echo " FAIL" + result=1 + else + echo " PASS" + result=0 + fi + else + printf "%s " "$test" + run_test $test $random_seed + result=$? + echo + fi + + case $result in + 0) passed=(${passed[@]} $test);; + 9) skipped=(${skipped[@]} $test);; + *) failed=(${failed[@]} $test);; + esac +done + +echo +echo "SUMMARY:" +echo " TOTAL $[${#passed[@]} + ${#failed[@]} + ${#skipped[@]}]" +echo " PASSED ${#passed[@]}" +echo " FAILED ${#failed[@]} (${failed[@]}) (${failed_seeds[@]})" +echo " SKIPPED ${#skipped[@]} (${skipped[@]})" + +[ ${#failed[@]} -eq 0 ] diff --git a/test/simulation/test.common b/test/simulation/test.common new file mode 100644 index 0000000..70bbde1 --- /dev/null +++ b/test/simulation/test.common @@ -0,0 +1,528 @@ +# Copyright (C) 2013-2014 Miroslav Lichvar <mlichvar@redhat.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +export LC_ALL=C +export PATH=../../:$PATH +export CLKNETSIM_PATH=${CLKNETSIM_PATH:-clknetsim} + +if [ ! -x $CLKNETSIM_PATH/clknetsim ]; then + echo "SKIP (clknetsim not found)" + exit 9 +fi + +. $CLKNETSIM_PATH/clknetsim.bash + +# Default test testings + +default_limit=10000 +default_primary_time_offset=0.0 +default_time_offset=1e-1 +default_freq_offset=1e-4 +default_base_delay=1e-4 +default_jitter=1e-4 +default_jitter_asymmetry=0.0 +default_wander=1e-9 +default_refclock_jitter="" +default_refclock_offset=0.0 + +default_update_interval=0 +default_shift_pll=2 + +default_server_strata=1 +default_servers=1 +default_clients=1 +default_peers=0 +default_falsetickers=0 +default_server_start=0.0 +default_client_start=0.0 +default_chronyc_start=1000.0 +default_server_step="" +default_client_step="" + +default_client_server_conf="" +default_server_server_options="" +default_client_server_options="" +default_server_peer_options="" +default_server_lpeer_options="" +default_server_rpeer_options="" +default_client_peer_options="" +default_client_lpeer_options="" +default_client_rpeer_options="" +default_server_conf="" +default_client_conf="" +default_chronyc_conf="" +default_server_chronyd_options="" +default_client_chronyd_options="" +default_chronyc_options="" + +default_time_max_limit=1e-3 +default_freq_max_limit=5e-4 +default_time_rms_limit=3e-4 +default_freq_rms_limit=1e-5 +default_min_sync_time=120 +default_max_sync_time=210 + +default_client_min_mean_out_interval=0.0 +default_client_max_min_out_interval=inf + +default_cmdmon_unix=1 +default_dns=0 + +# Initialize test settings from their defaults +for defoptname in ${!default_*}; do + optname=${defoptname#default_} + [ -z "${!optname}" ] && declare "$optname"="${!defoptname}" +done + +test_start() { + rm -rf tmp/* + echo "Testing $@:" + + check_config_h 'FEAT_NTP 1' || test_skip +} + +test_pass() { + echo "PASS" + exit 0 +} + +test_fail() { + echo "FAIL" + exit 1 +} + +test_skip() { + echo "SKIP" + exit 9 +} + +test_ok() { + pad_line + echo -e "\tOK" + return 0 +} + +test_bad() { + pad_line + echo -e "\tBAD" + return 1 +} + +test_error() { + pad_line + echo -e "\tERROR" + return 1 +} + +msg_length=0 +pad_line() { + local line_length=56 + [ $msg_length -lt $line_length ] && \ + printf "%$[$line_length - $msg_length]s" "" + msg_length=0 +} + +# Print aligned message +test_message() { + local level=$1 eol=$2 + shift 2 + local msg="$*" + + while [ $level -gt 0 ]; do + echo -n " " + level=$[$level - 1] + msg_length=$[$msg_length + 2] + done + echo -n "$msg" + + msg_length=$[$msg_length + ${#msg}] + if [ $eol -ne 0 ]; then + echo + msg_length=0 + fi +} + +get_wander_expr() { + local scaled_wander + + scaled_wander=$(awk "BEGIN {print $wander / \ + sqrt($update_interval < 0 ? 2^-($update_interval) : 1)}") + + echo "(+ $freq_offset (sum (* $scaled_wander (normal))))" +} + + +get_delay_expr() { + local direction=$1 asym + + if [ $jitter_asymmetry == "0.0" ]; then + asym="" + elif [ $direction = "up" ]; then + asym=$(awk "BEGIN {print 1 - 2 * $jitter_asymmetry}") + elif [ $direction = "down" ]; then + asym=$(awk "BEGIN {print 1 + 2 * $jitter_asymmetry}") + fi + echo "(+ $base_delay (* $asym $jitter (exponential)))" +} + +get_refclock_expr() { + echo "(+ $refclock_offset (* $refclock_jitter (normal)))" +} + +get_chronyd_nodes() { + echo $[$servers * $server_strata + $clients] +} + +get_node_name() { + local index=$1 + + if [ $dns -ne 0 ]; then + echo "node$index.net1.clk" + else + echo "192.168.123.$index" + fi +} + +get_chronyd_conf() { + local i stratum=$1 peer=$2 + + if [ $stratum -eq 1 ]; then + echo "local stratum 1" + echo "$server_conf" + elif [ $stratum -le $server_strata ]; then + for i in $(seq 1 $servers); do + echo "server $(get_node_name $[$servers * ($stratum - 2) + $i]) $server_server_options" + done + for i in $(seq 1 $peers); do + [ $i -eq $peer -o $i -gt $servers ] && continue + echo -n "peer $(get_node_name $[$servers * ($stratum - 1) + $i]) $server_peer_options " + [ $i -lt $peer ] && echo "$server_lpeer_options" || echo "$server_rpeer_options" + done + echo "$server_conf" + else + echo "deny" + if [ -n "$client_server_conf" ]; then + echo "$client_server_conf" + else + for i in $(seq 1 $servers); do + echo "server $(get_node_name $[$servers * ($stratum - 2) + $i]) $client_server_options" + done + fi + for i in $(seq 1 $peers); do + [ $i -eq $peer -o $i -gt $clients ] && continue + echo -n "peer $(get_node_name $[$servers * ($stratum - 1) + $i]) $client_peer_options " + [ $i -lt $peer ] && echo "$client_lpeer_options" || echo "$client_rpeer_options" + done + echo "$client_conf" + fi +} + +# Check if chrony was built with specified option in config.h +check_config_h() { + local pattern=$1 + grep -q "^#define $pattern" ../../config.h +} + +# Check if the clock was well synchronized +check_sync() { + local i sync_time max_time_error max_freq_error ret=0 + local rms_time_error rms_freq_error + + test_message 2 1 "checking clock sync time, max/rms time/freq error:" + + for i in $(seq 1 $(get_chronyd_nodes)); do + [ $i -gt $[$servers * $server_strata] ] || continue + + sync_time=$(find_sync tmp/log.offset tmp/log.freq $i \ + $time_max_limit $freq_max_limit 1.0) + max_time_error=$(get_stat 'Maximum absolute offset' $i) + max_freq_error=$(get_stat 'Maximum absolute frequency' $i) + rms_time_error=$(get_stat 'RMS offset' $i) + rms_freq_error=$(get_stat 'RMS frequency' $i) + + test_message 3 0 "node $i: $sync_time $(printf '%.2e %.2e %.2e %.2e' \ + $max_time_error $max_freq_error $rms_time_error $rms_freq_error)" + + check_stat $sync_time $min_sync_time $max_sync_time && \ + check_stat $max_time_error 0.0 $time_max_limit && \ + check_stat $max_freq_error 0.0 $freq_max_limit && \ + check_stat $rms_time_error 0.0 $time_rms_limit && \ + check_stat $rms_freq_error 0.0 $freq_rms_limit && \ + test_ok || test_bad + + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Check if chronyd exited properly +check_chronyd_exit() { + local i ret=0 + + test_message 2 1 "checking chronyd exit:" + + for i in $(seq 1 $(get_chronyd_nodes)); do + test_message 3 0 "node $i:" + + grep -q 'chronyd exiting' tmp/log.$i && \ + ! grep -q 'Adjustment.*exceeds.*exiting' tmp/log.$i && \ + ! grep -q 'Assertion.*failed' tmp/log.$i && \ + test_ok || test_bad + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Check for problems in source selection +check_source_selection() { + local i ret=0 + + test_message 2 1 "checking source selection:" + + for i in $(seq $[$servers * $server_strata + 1] $(get_chronyd_nodes)); do + test_message 3 0 "node $i:" + + ! grep -q 'no majority\|no selectable sources' tmp/log.$i && \ + grep -q 'Selected source' tmp/log.$i && \ + test_ok || test_bad + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Check if incoming and outgoing packet intervals are sane +check_packet_interval() { + local i ret=0 mean_in_interval mean_out_interval min_in_interval min_out_interval + + test_message 2 1 "checking mean/min incoming/outgoing packet interval:" + + for i in $(seq 1 $(get_chronyd_nodes)); do + mean_in_interval=$(get_stat 'Mean incoming packet interval' $i) + mean_out_interval=$(get_stat 'Mean outgoing packet interval' $i) + min_in_interval=$(get_stat 'Minimum incoming packet interval' $i) + min_out_interval=$(get_stat 'Minimum outgoing packet interval' $i) + + test_message 3 0 "node $i: $(printf '%.2e %.2e %.2e %.2e' \ + $mean_in_interval $mean_out_interval $min_in_interval $min_out_interval)" + + # Check that the mean intervals are non-zero and shorter than + # limit, incoming is not longer than outgoing for stratum 1 + # servers, outgoing is not longer than incoming for clients, + # and the minimum outgoing interval is not shorter than the NTP + # sampling separation or iburst interval for clients + nodes=$[$servers * $server_strata + $clients] + check_stat $mean_in_interval 0.1 inf && \ + check_stat $mean_out_interval 0.1 inf && \ + ([ $i -gt $servers ] || \ + check_stat $mean_in_interval 0.0 $mean_out_interval 10*$jitter) && \ + ([ $i -le $[$servers * $server_strata] ] || \ + check_stat $mean_out_interval $client_min_mean_out_interval \ + $mean_in_interval 10*$jitter) && \ + ([ $i -le $[$servers * $server_strata] ] || \ + check_stat $min_out_interval \ + $([ $servers -gt 1 ] && echo 0.18 || echo 1.8) \ + $client_max_min_out_interval) && \ + test_ok || test_bad + + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Compare chronyc output with specified pattern +check_chronyc_output() { + local i ret=0 pattern=$1 + + test_message 2 1 "checking chronyc output:" + + for i in $(seq $[$(get_chronyd_nodes) + 1] $[$(get_chronyd_nodes) + $clients]); do + test_message 3 0 "node $i:" + + [[ "$(cat tmp/log.$i)" =~ $pattern ]] && \ + test_ok || test_bad + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Check the number of messages matching a pattern in the client logs +check_log_messages() { + local i count ret=0 pattern=$1 min=$2 max=$3 + + test_message 2 1 "checking number of messages \"$pattern\":" + + for i in $(seq $[$servers * $server_strata + 1] $(get_chronyd_nodes)); do + count=$(grep "$pattern" tmp/log.$i | wc -l) + test_message 3 0 "node $i: $count" + + [ "$min" -le "$count" ] && [ "$count" -le "$max" ] && \ + test_ok || test_bad + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Check the number of messages matching a pattern in a specified file +check_file_messages() { + local i count ret=0 pattern=$1 min=$2 max=$3 + shift 3 + + test_message 2 1 "checking number of messages \"$pattern\":" + + for i; do + count=$(grep "$pattern" tmp/$i | wc -l) + test_message 3 0 "$i: $count" + + [ "$min" -le "$count" ] && [ "$count" -le "$max" ] && \ + test_ok || test_bad + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Check if only NTP port (123) was used +check_packet_port() { + local i ret=0 port=123 + + test_message 2 1 "checking port numbers in packet log:" + + for i in $(seq 1 $(get_chronyd_nodes)); do + test_message 3 0 "node $i:" + + grep -E -q "^([0-9e.+-]+ ){5}$port " tmp/log.packets && \ + ! grep -E "^[0-9e.+-]+ $i " tmp/log.packets | \ + grep -E -q -v "^([0-9e.+-]+ ){5}$port " && \ + test_ok || test_bad + [ $? -eq 0 ] || ret=1 + done + + return $ret +} + +# Print test settings which differ from default value +print_nondefaults() { + local defoptname optname + + test_message 2 1 "non-default settings:" + for defoptname in ${!default_*}; do + optname=${defoptname#default_} + [ "${!defoptname}" = "${!optname}" ] || \ + test_message 3 1 $optname=${!optname} + done +} + +run_simulation() { + local nodes=$1 + + test_message 2 0 "running simulation:" + + start_server $nodes \ + -n 2 \ + -o tmp/log.offset -f tmp/log.freq -p tmp/log.packets \ + -R $(awk "BEGIN {print $update_interval < 0 ? 2^-($update_interval) : 1}") \ + -r $(awk "BEGIN {print $max_sync_time * 2^$update_interval}") \ + -l $(awk "BEGIN {print $limit * 2^$update_interval}") && test_ok || test_error +} + +run_test() { + local i j n stratum node nodes step start freq offset conf options + + test_message 1 1 "network with $servers*$server_strata servers and $clients clients:" + print_nondefaults + + nodes=$(get_chronyd_nodes) + [ -n "$chronyc_conf" ] && nodes=$[$nodes + $clients] + + export CLKNETSIM_UNIX_SUBNET=$[$cmdmon_unix != 0 ? 2 : 0] + + for i in $(seq 1 $nodes); do + echo "node${i}_shift_pll = $shift_pll" + for j in $(seq 1 $nodes); do + echo "node${i}_delay${j} = $(get_delay_expr up)" + echo "node${j}_delay${i} = $(get_delay_expr down)" + done + done > tmp/conf + + node=1 + + for stratum in $(seq 1 $[$server_strata + 1]); do + [ $stratum -le $server_strata ] && n=$servers || n=$clients + + for i in $(seq 1 $n); do + test_message 2 0 "starting node $node:" + if [ $stratum -eq 1 ]; then + step=$server_step + start=$server_start + freq="" + [ $i -le $falsetickers ] && + offset=$i.0 || offset=$primary_time_offset + options=$server_chronyd_options + elif [ $stratum -le $server_strata ]; then + step=$server_step + start=$server_start + freq=$(get_wander_expr) + offset=0.0 + options=$server_chronyd_options + else + step=$client_step + start=$client_start + freq=$(get_wander_expr) + offset=$time_offset + options=$client_chronyd_options + fi + + conf=$(get_chronyd_conf $stratum $i $n) + + [ -z "$freq" ] || echo "node${node}_freq = $freq" >> tmp/conf + [ -z "$step" ] || echo "node${node}_step = $step" >> tmp/conf + [ -z "$refclock_jitter" ] || \ + echo "node${node}_refclock = $(get_refclock_expr)" >> tmp/conf + echo "node${node}_offset = $offset" >> tmp/conf + echo "node${node}_start = $start" >> tmp/conf + start_client $node chronyd "$conf" "" "$options" && \ + test_ok || test_error + + [ $? -ne 0 ] && return 1 + node=$[$node + 1] + done + done + + for i in $(seq 1 $[$nodes - $node + 1]); do + test_message 2 0 "starting node $node:" + + options=$([ $dns -eq 0 ] && printf "%s" "-n") + if [ $cmdmon_unix -ne 0 ]; then + options+=" -h /clknetsim/unix/$[$node - $clients]:1" + else + options+=" -h $(get_node_name $[$node - $clients])" + fi + + echo "node${node}_start = $chronyc_start" >> tmp/conf + start_client $node chronyc "$chronyc_conf" "" "$options $chronyc_options" && \ + test_ok || test_error + + [ $? -ne 0 ] && return 1 + node=$[$node + 1] + done + + run_simulation $nodes +} diff --git a/test/system/001-minimal b/test/system/001-minimal new file mode 100755 index 0000000..107fa3f --- /dev/null +++ b/test/system/001-minimal @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "minimal configuration" + +minimal_config=1 + +start_chronyd || test_fail +stop_chronyd || test_fail +check_chronyd_messages || test_fail + +test_pass diff --git a/test/system/002-extended b/test/system/002-extended new file mode 100755 index 0000000..7a6734f --- /dev/null +++ b/test/system/002-extended @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "extended configuration" + +start_chronyd || test_fail +wait_for_sync || test_fail +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_files || test_fail + +test_pass diff --git a/test/system/003-memlock b/test/system/003-memlock new file mode 100755 index 0000000..e4ab1bf --- /dev/null +++ b/test/system/003-memlock @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "memory locking" + +extra_chronyd_options="-m" + +start_chronyd || test_fail +wait_for_sync || test_fail +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_files || test_fail + +test_pass diff --git a/test/system/004-priority b/test/system/004-priority new file mode 100755 index 0000000..bf8a04b --- /dev/null +++ b/test/system/004-priority @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "process priority" + +extra_chronyd_options="-P 1" + +start_chronyd || test_fail +wait_for_sync || test_fail +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_files || test_fail + +test_pass diff --git a/test/system/006-privdrop b/test/system/006-privdrop new file mode 100755 index 0000000..6d7b0c9 --- /dev/null +++ b/test/system/006-privdrop @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +. ./test.common + +check_chronyd_features PRIVDROP || test_skip "PRIVDROP support disabled" + +user="nobody" + +test_start "dropping of root privileges" + +minimal_config=1 + +start_chronyd || test_fail +stop_chronyd || test_fail +check_chronyd_messages || test_fail + +test_pass diff --git a/test/system/007-cmdmon b/test/system/007-cmdmon new file mode 100755 index 0000000..04d14c2 --- /dev/null +++ b/test/system/007-cmdmon @@ -0,0 +1,181 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "chronyc commands" + +start_chronyd || test_fail +wait_for_sync || test_fail + +has_ipv6=$(check_chronyd_features IPV6 && ping6 -c 1 ::1 > /dev/null 2>&1 && echo 1 || echo 0) + +for command in \ + "allow 1.2.3.4" \ + "deny 1.2.3.4" \ + "cmddeny" \ + "cmdallow" \ + "cmddeny 1.2.3.4" \ + "cmdallow 1.2.3.4" \ + "add server 127.123.1.1" \ + "delete 127.123.1.1" \ + "burst 1/1" \ + "cyclelogs" \ + "dfreq 1.0e-3" \ + "doffset -0.1" \ + "dump" \ + "offline" \ + "local off" \ + "local" \ + "online" \ + "onoffline" \ + "maxdelay $server 1e-1" \ + "maxdelaydevratio $server 5.0" \ + "maxdelayratio $server 3.0" \ + "maxpoll $server 12" \ + "maxupdateskew $server 10.0" \ + "minpoll $server 10" \ + "minstratum $server 1" \ + "polltarget $server 10" \ + "refresh" \ + "rekey" \ + "reload sources" \ + "reselect" \ + "reselectdist 1e-3" \ + "reset sources" \ + "smoothtime reset" \ + "smoothtime activate" \ +; do + run_chronyc "$command" || test_fail + check_chronyc_output "^200 OK$" || test_fail +done + +run_chronyc "accheck $server" || test_fail +check_chronyc_output "^208 Access allowed$" || test_fail +run_chronyc "accheck 1.2.3.4" || test_fail +check_chronyc_output "^209 Access denied$" || test_fail + +run_chronyc "cmdaccheck 1.2.3.4" || test_fail +check_chronyc_output "^208 Access allowed$" || test_fail + +run_chronyc "authdata" || test_fail +check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen +========================================================================= +127\.0\.0\.1 - 0 0 0 - 0 0 0 0$" \ + || test_chronyc + +run_chronyc "clients" || test_fail +check_chronyc_output "^Hostname NTP Drop Int IntL Last Cmd Drop Int Last +=============================================================================== +127\.0\.0\.1 [0-9 ]+ 0 [-0-9 ]+ - [ 0-9]+ 0 0 - -$" \ + || test_fail + +run_chronyc "ntpdata $server" || test_fail +check_chronyc_output "^Remote address : 127\.0\.0\.1 \(7F000001\) +Remote port : [0-9]+ +Local address : 127\.0\.0\.1 \(7F000001\) +Leap status : Normal +Version : 4 +Mode : Server +Stratum : 10 +Poll interval : (-6|[0-9]+) \([0-9]+ seconds\) +Precision : [0-9 +-]+ \(0\.[0-9]+ seconds\) +Root delay : 0\.000000 seconds +Root dispersion : 0\.000000 seconds +Reference ID : 7F7F0101 \(\) +Reference time : [A-Za-z0-9: ]+ +Offset : [+-]0\.......... seconds +Peer delay : 0\.......... seconds +Peer dispersion : 0\.......... seconds +Response time : 0\.......... seconds +Jitter asymmetry: \+0\.00 +NTP tests : 111 111 1110 +Interleaved : No +Authenticated : No +TX timestamping : (Daemon|Kernel) +RX timestamping : (Daemon|Kernel) +Total TX : [0-9]+ +Total RX : [0-9]+ +Total valid RX : [0-9]+ +Total good RX : [0-9]+$" || test_fail + +run_chronyc "selectdata" || test_fail +check_chronyc_output "^S Name/IP Address Auth COpts EOpts Last Score Interval Leap +======================================================================= +s 127\.0\.0\.1 N ----- ----- 0 1\.0 \+0ns \+0ns \?$" || test_fail + +run_chronyc "serverstats" || test_fail +check_chronyc_output "^NTP packets received : [0-9]+ +NTP packets dropped : 0 +Command packets received : [0-9]+ +Command packets dropped : 0 +Client log records dropped : 0 +NTS-KE connections accepted: 0 +NTS-KE connections dropped : 0 +Authenticated NTP packets : 0 +Interleaved NTP packets : 0 +NTP timestamps held : 0 +NTP timestamp span : 0$"|| test_fail + +run_chronyc "manual on" || test_fail +check_chronyc_output "^200 OK$" || test_fail + +run_chronyc "settime now" || test_fail +check_chronyc_output "^200 OK +Clock was.*$" || test_fail + +run_chronyc "manual delete 0" || test_fail +check_chronyc_output "^200 OK$" || test_fail + +run_chronyc "settime now" || test_fail +check_chronyc_output "^200 OK +Clock was.*$" || test_fail + +run_chronyc "manual list" || test_fail +check_chronyc_output "^210 n_samples = 1 +# Date Time\(UTC\) Slewed Original Residual +======================================================= + 0.*$" || test_fail + +run_chronyc "manual reset" || test_fail +check_chronyc_output "^200 OK$" || test_fail + +run_chronyc "manual off" || test_fail +check_chronyc_output "^200 OK$" || test_fail + +run_chronyc "shutdown" || test_fail +check_chronyc_output "^200 OK$" || test_fail + +stop_chronyd || test_fail +check_chronyd_messages || test_fail +start_chronyd || test_fail + +run_chronyc "makestep" && test_fail +check_chronyc_output "500 Failure" || test_fail + +run_chronyc "trimrtc" && test_fail +check_chronyc_output "513 RTC driver not running" || test_fail + +run_chronyc "writertc" && test_fail +check_chronyc_output "513 RTC driver not running" || test_fail + +chronyc_host=127.0.0.1 + +run_chronyc "tracking" || test_fail +check_chronyc_output "^Reference ID" || test_fail + +run_chronyc "makestep" && test_fail +check_chronyc_output "^501 Not authorised$" || test_fail + +if [ "$has_ipv6" = "1" ]; then + chronyc_host=::1 + + run_chronyc "tracking" || test_fail + check_chronyc_output "^Reference ID" || test_fail + + run_chronyc "makestep" && test_fail + check_chronyc_output "^501 Not authorised$" || test_fail +fi + +stop_chronyd || test_fail + +test_pass diff --git a/test/system/008-confload b/test/system/008-confload new file mode 100755 index 0000000..a7fc1d5 --- /dev/null +++ b/test/system/008-confload @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "loading of configuration" + +minimal_config=1 +extra_chronyd_directives=" +include $TEST_DIR/conf1.d/conf.1 +confdir $TEST_DIR/conf1.d +confdir $TEST_DIR/conf2.d $TEST_DIR/conf3.d $TEST_DIR/conf4.d +sourcedir $TEST_DIR/conf5.d +include $TEST_DIR/conf1.d/conf.2" + +mkdir $TEST_DIR/conf{1,2,3,4,5}.d + +echo "server 127.123.1.1" > $TEST_DIR/conf1.d/conf.1 +echo "server 127.123.1.2" > $TEST_DIR/conf1.d/conf.2 +echo "server 127.123.1.3" > $TEST_DIR/conf1.d/3.conf +echo "server 127.123.1.4" > $TEST_DIR/conf1.d/4.conf +echo "server 127.123.2.2" > $TEST_DIR/conf2.d/2.conf +echo "server 127.123.2.3" > $TEST_DIR/conf2.d/3.conf +echo "server 127.123.3.1" > $TEST_DIR/conf3.d/1.conf +echo "server 127.123.3.2" > $TEST_DIR/conf3.d/2.conf +echo "server 127.123.3.3" > $TEST_DIR/conf3.d/3.conf +echo "server 127.123.4.1" > $TEST_DIR/conf4.d/1.conf +echo "server 127.123.4.2" > $TEST_DIR/conf4.d/2.conf +echo "server 127.123.4.3" > $TEST_DIR/conf4.d/3.conf +echo "server 127.123.4.4" > $TEST_DIR/conf4.d/4.conf +echo "server 127.123.5.1" > $TEST_DIR/conf5.d/1.sources +echo "server 127.123.5.2" > $TEST_DIR/conf5.d/2.sources +echo "server 127.123.5.3" > $TEST_DIR/conf5.d/3.sources + +start_chronyd || test_fail + +run_chronyc "sources" || test_fail +check_chronyc_output "^[^=]* +=* +.. 127\.123\.1\.1 [^^]* +.. 127\.123\.1\.3 [^^]* +.. 127\.123\.1\.4 [^^]* +.. 127\.123\.3\.1 [^^]* +.. 127\.123\.2\.2 [^^]* +.. 127\.123\.2\.3 [^^]* +.. 127\.123\.4\.4 [^^]* +.. 127\.123\.1\.2 [^^]* +.. 127\.123\.5\.1 [^^]* +.. 127\.123\.5\.2 [^^]* +.. 127\.123\.5\.3 [^^]*$" || test_fail + +rm $TEST_DIR/conf5.d/1.sources +echo "server 127.123.5.2 minpoll 7" > $TEST_DIR/conf5.d/2.sources +echo > $TEST_DIR/conf5.d/3.sources +echo "server 127.123.5.4" > $TEST_DIR/conf5.d/4.sources + +run_chronyc "reload sources" || test_fail + +run_chronyc "sources" || test_fail +check_chronyc_output "^[^=]* +=* +.. 127\.123\.1\.1 [^^]* +.. 127\.123\.1\.3 [^^]* +.. 127\.123\.1\.4 [^^]* +.. 127\.123\.3\.1 [^^]* +.. 127\.123\.2\.2 [^^]* +.. 127\.123\.2\.3 [^^]* +.. 127\.123\.4\.4 [^^]* +.. 127\.123\.1\.2 *[05] 6 [^^]* +.. 127\.123\.5\.2 *[05] 7 [^^]* +.. 127\.123\.5\.4 [^^]*$" || test_fail + +stop_chronyd || test_fail + +test_pass diff --git a/test/system/009-binddevice b/test/system/009-binddevice new file mode 100755 index 0000000..fc64ae2 --- /dev/null +++ b/test/system/009-binddevice @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +. ./test.common + +[ "$(uname -s)" = "Linux" ] || test_skip "non-Linux system" + +test_start "binddevice directives" + +extra_chronyd_directives=" +binddevice lo +bindacqdevice lo +bindcmddevice lo" + +start_chronyd || test_fail +wait_for_sync || test_fail + +run_chronyc "ntpdata $server" || test_fail +check_chronyc_output "^Remote address" || test_fail + +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_files || test_fail + +test_pass diff --git a/test/system/010-nts b/test/system/010-nts new file mode 100755 index 0000000..a0b366e --- /dev/null +++ b/test/system/010-nts @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +. ./test.common + +check_chronyd_features NTS || test_skip "NTS support disabled" +certtool --help &> /dev/null || test_skip "certtool missing" + +test_start "NTS authentication" + +cat > $TEST_DIR/cert.cfg <<EOF +cn = "chrony-nts-test" +dns_name = "chrony-nts-test" +ip_address = "$server" +serial = 001 +activation_date = "$[$(date '+%Y') - 1]-01-01 00:00:00 UTC" +expiration_date = "$[$(date '+%Y') + 2]-01-01 00:00:00 UTC" +signing_key +encryption_key +EOF + +certtool --generate-privkey --key-type=ed25519 --outfile $TEST_DIR/server.key \ + &> $TEST_DIR/certtool.log +certtool --generate-self-signed --load-privkey $TEST_DIR/server.key \ + --template $TEST_DIR/cert.cfg --outfile $TEST_DIR/server.crt &>> $TEST_DIR/certtool.log +chown $user $TEST_DIR/server.* + +ntpport=$(get_free_port) +ntsport=$(get_free_port) + +server_options="port $ntpport nts ntsport $ntsport" +extra_chronyd_directives=" +port $ntpport +ntsport $ntsport +ntsserverkey $TEST_DIR/server.key +ntsservercert $TEST_DIR/server.crt +ntstrustedcerts $TEST_DIR/server.crt +ntsdumpdir $TEST_LIBDIR +ntsprocesses 3" + +start_chronyd || test_fail +wait_for_sync || test_fail + +run_chronyc "authdata" || test_fail +check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen +========================================================================= +127\.0\.0\.1 NTS 1 15 256 [0-9] 0 0 [78] 100$" || test_fail + +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_files || test_fail + +server_options="port $ntpport nts ntsport $((ntsport + 1))" + +start_chronyd || test_fail +wait_for_sync || test_fail + +run_chronyc "authdata" || test_fail +check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen +========================================================================= +127\.0\.0\.1 NTS 1 15 256 [0-9] 0 0 [78] 100$" || test_fail + +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_files || test_fail + +test_pass diff --git a/test/system/099-scfilter b/test/system/099-scfilter new file mode 100755 index 0000000..6b098ac --- /dev/null +++ b/test/system/099-scfilter @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +. ./test.common + +check_chronyd_features SCFILTER || test_skip "SCFILTER support disabled" + +test_start "system call filter in non-destructive tests" + +for level in "-1" "1" "-2" "2"; do + test_message 1 1 "level $level:" + for test in 0[0-8][0-9]-*[^_]; do + test_message 2 0 "$test" + TEST_SCFILTER=$level "./$test" > /dev/null 2> /dev/null + result=$? + + if [ $result != 0 ] && [ $result != 9 ] ; then + test_bad + test_fail + fi + test_ok + done +done + +test_pass diff --git a/test/system/100-clockupdate b/test/system/100-clockupdate new file mode 100755 index 0000000..191e461 --- /dev/null +++ b/test/system/100-clockupdate @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "update of system clock" + +clock_control=1 +minimal_config=1 + +start_chronyd || test_fail +run_chronyc "dfreq 1e-3" || test_fail +check_chronyc_output "200 OK" || test_fail + +before=$(date '+%s') +run_chronyc "doffset -1.0" || test_fail +check_chronyc_output "200 OK" || test_fail +run_chronyc "makestep" || test_fail +check_chronyc_output "200 OK" || test_fail +after=$(date '+%s') + +test_message 1 0 "checking system clock" +[ "$before" -lt "$after" ] && test_ok || test_bad || test_fail + +run_chronyc "doffset 1.0" || test_fail +run_chronyc "makestep" || test_fail +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_message_count "System clock was stepped by" 2 2 || test_fail + +test_pass diff --git a/test/system/101-rtc b/test/system/101-rtc new file mode 100755 index 0000000..68bce68 --- /dev/null +++ b/test/system/101-rtc @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +. ./test.common + +check_chronyd_features RTC || test_skip "RTC support disabled" +[ -c "/dev/rtc" ] || test_skip "missing /dev/rtc" + +test_start "real-time clock" + +minimal_config=1 +extra_chronyd_options="-s" +extra_chronyd_directives="rtcfile $TEST_DIR/rtcfile" +echo "1 $(date +%s) 0.0 0.0" > "$TEST_DIR/rtcfile" + +start_chronyd || test_fail +stop_chronyd || test_fail +check_chronyd_message_count "\(clock off from RTC\|RTC time before last\|Could not \(enable\|disable\) RTC interrupt\)" 1 1 || test_fail + +test_pass diff --git a/test/system/102-hwtimestamp b/test/system/102-hwtimestamp new file mode 100755 index 0000000..6b86d20 --- /dev/null +++ b/test/system/102-hwtimestamp @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +. ./test.common + +[ "$(uname -s)" = "Linux" ] || test_skip "non-Linux system" + +hwts_iface="" +for iface_path in /sys/class/net/*; do + iface=$(basename "$iface_path") + if ethtool -T "$iface" 2> /dev/null | grep -q ' all\($\| \)'; then + hwts_iface="$iface" + break + fi +done + +[ -n "$hwts_iface" ] || test_skip "no HW timestamping interface found" + +test_start "hardware timestamping" + +minimal_config=1 +extra_chronyd_directives="hwtimestamp $hwts_iface" + +start_chronyd || test_fail +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_message_count "Enabled HW timestamping on $hwts_iface" 1 1 || test_fail + +test_pass diff --git a/test/system/103-refclock b/test/system/103-refclock new file mode 100755 index 0000000..e5b74e0 --- /dev/null +++ b/test/system/103-refclock @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +. ./test.common + +check_chronyd_features REFCLOCK || test_skip "refclock support disabled" + +test_start "reference clocks" + +extra_chronyd_directives=" +refclock SOCK $TEST_DIR/refclock.sock +refclock SHM 100" + +start_chronyd || test_fail +wait_for_sync || test_fail +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_files || test_fail + +test_pass diff --git a/test/system/104-systemdirs b/test/system/104-systemdirs new file mode 100755 index 0000000..15508dc --- /dev/null +++ b/test/system/104-systemdirs @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +TEST_LIBDIR=${CHRONY_LIBDIR:-/var/lib/chrony} +TEST_LOGDIR=${CHRONY_LOGDIR:-/var/log/chrony} +TEST_RUNDIR=${CHRONY_RUNDIR:-/var/run/chrony} + +. ./test.common + +user=$(ls -ld "$TEST_RUNDIR" 2> /dev/null | awk '{print $3}') + +test_start "system directories" + +start_chronyd || test_fail +wait_for_sync || test_fail +stop_chronyd || test_fail +check_chronyd_messages || test_fail +check_chronyd_files || test_fail + +test_pass diff --git a/test/system/199-scfilter b/test/system/199-scfilter new file mode 100755 index 0000000..29b7cc3 --- /dev/null +++ b/test/system/199-scfilter @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +. ./test.common + +check_chronyd_features SCFILTER || test_skip "SCFILTER support disabled" + +test_start "system call filter in destructive tests" + +for level in "-1" "1" "-2" "2"; do + test_message 1 1 "level $level:" + for test in 1[0-8][0-9]-*[^_]; do + test_message 2 0 "$test" + TEST_SCFILTER=$level "./$test" > /dev/null 2> /dev/null + result=$? + + if [ $result != 0 ] && [ $result != 9 ] ; then + test_bad + test_fail + fi + test_ok + done +done + +test_pass diff --git a/test/system/run b/test/system/run new file mode 100755 index 0000000..5516ed4 --- /dev/null +++ b/test/system/run @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +print_help() { + echo "$1 [-a] [-d] [TEST]..." +} + +run_test() { + local result name=$1 + + if [ $destructive -ne 1 ] && [[ "$name" == 1[0-9][0-9]-* ]]; then + echo "SKIP (destructive test)" + return 9 + fi + + ./$name + result=$? + + if [ $result -ne 0 -a $result -ne 9 ]; then + if [ $abort_on_fail -ne 0 ]; then + exit 1 + fi + fi + + return $result +} + +passed=() failed=() skipped=() + +abort_on_fail=0 +destructive=0 + +while getopts ":ad" opt; do + case $opt in + a) abort_on_fail=1;; + d) destructive=1;; + *) print_help "$0"; exit 3;; + esac +done + +shift $[$OPTIND - 1] + +[ $# -gt 0 ] && tests=($@) || tests=([0-9]*-*[^_]) + +for test in "${tests[@]}"; do + printf "%s " "$test" + run_test $test + result=$? + echo + + case $result in + 0) passed=(${passed[@]} $test);; + 9) skipped=(${skipped[@]} $test);; + *) failed=(${failed[@]} $test);; + esac +done + +echo +echo "SUMMARY:" +echo " TOTAL $[${#passed[@]} + ${#failed[@]} + ${#skipped[@]}]" +echo " PASSED ${#passed[@]}" +echo " FAILED ${#failed[@]} (${failed[@]})" +echo " SKIPPED ${#skipped[@]} (${skipped[@]})" + +[ ${#failed[@]} -eq 0 ] diff --git a/test/system/test.common b/test/system/test.common new file mode 100644 index 0000000..7005c9e --- /dev/null +++ b/test/system/test.common @@ -0,0 +1,373 @@ +# Copyright (C) Miroslav Lichvar 2009 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +export LC_ALL=C +export PATH=${CHRONY_PATH:-../..}:$PATH + +TEST_DIR=${TEST_DIR:-$(pwd)/tmp} +TEST_LIBDIR=${TEST_LIBDIR:-$TEST_DIR} +TEST_LOGDIR=${TEST_LOGDIR:-$TEST_DIR} +TEST_RUNDIR=${TEST_RUNDIR:-$TEST_DIR} +TEST_SCFILTER=${TEST_SCFILTER:-0} + +test_start() { + check_chronyd_features NTP CMDMON || test_skip "NTP/CMDMON support disabled" + + [ "${#TEST_DIR}" -ge 5 ] || test_skip "invalid TEST_DIR" + + rm -rf "$TEST_DIR" + mkdir -p "$TEST_DIR" && chmod 700 "$TEST_DIR" || test_skip "could not create $TEST_DIR" + + [ -d "$TEST_LIBDIR" ] || test_skip "missing $TEST_LIBDIR" + [ -d "$TEST_LOGDIR" ] || test_skip "missing $TEST_LOGDIR" + [ -d "$TEST_RUNDIR" ] || test_skip "missing $TEST_RUNDIR" + + rm -f "$TEST_LIBDIR"/* "$TEST_LOGDIR"/* "$TEST_RUNDIR"/* + + if [ "$user" != "root" ]; then + id -u "$user" > /dev/null 2> /dev/null || test_skip "missing user $user" + chown "$user:$(id -g "$user")" "$TEST_DIR" || test_skip "could not chown $TEST_DIR" + su "$user" -s /bin/sh -c "touch $TEST_DIR/test" 2> /dev/null || \ + test_skip "$user cannot access $TEST_DIR" + rm "$TEST_DIR/test" + fi + + echo "Testing $*:" +} + +test_pass() { + echo "PASS" + exit 0 +} + +test_fail() { + echo "FAIL" + exit 1 +} + +test_skip() { + local msg=$1 + + [ -n "$msg" ] && echo "SKIP ($msg)" || echo "SKIP" + exit 9 +} + +test_ok() { + pad_line + echo -e "\tOK" + return 0 +} + +test_bad() { + pad_line + echo -e "\tBAD" + return 1 +} + +test_error() { + pad_line + echo -e "\tERROR" + return 1 +} + +chronyd=$(command -v chronyd) +chronyc=$(command -v chronyc) + +[ $EUID -eq 0 ] || test_skip "not root" + +[ -x "$chronyd" ] || test_skip "chronyd not found" +[ -x "$chronyc" ] || test_skip "chronyc not found" + +if netstat -aln > /dev/null 2> /dev/null; then + port_list_command="netstat -aln" +elif ss -atun > /dev/null 2> /dev/null; then + port_list_command="ss -atun" +else + test_skip "missing netstat or ss" +fi + +# Default test testings +default_minimal_config=0 +default_extra_chronyd_directives="" +default_extra_chronyd_options="" +default_clock_control=0 +default_server=127.0.0.1 +default_server_name=127.0.0.1 +default_server_options="" +default_user=root + +# Initialize test settings from their defaults +for defoptname in ${!default_*}; do + optname=${defoptname#default_} + [ -z "${!optname}" ] && declare "$optname"="${!defoptname}" +done + +msg_length=0 +pad_line() { + local line_length=56 + [ $msg_length -lt $line_length ] && \ + printf "%$((line_length - msg_length))s" "" + msg_length=0 +} + +# Print aligned message +test_message() { + local level=$1 eol=$2 + shift 2 + local msg="$*" + + while [ "$level" -gt 0 ]; do + echo -n " " + level=$((level - 1)) + msg_length=$((msg_length + 2)) + done + echo -n "$msg" + + msg_length=$((msg_length + ${#msg})) + if [ "$eol" -ne 0 ]; then + echo + msg_length=0 + fi +} + +# Check if chronyd has specified features +check_chronyd_features() { + local feature features + + features=$($chronyd -v | sed 's/.*(\(.*\)).*/\1/') + + for feature; do + echo "$features" | grep -q "+$feature" || return 1 + done +} + +# Print test settings which differ from default value +print_nondefaults() { + local defoptname optname + + test_message 1 1 "non-default settings:" + for defoptname in ${!default_*}; do + optname=${defoptname#default_} + [ "${!defoptname}" = "${!optname}" ] || \ + test_message 2 1 "$optname"=${!optname} + done +} + +get_conffile() { + echo "$TEST_DIR/chronyd.conf" +} + +get_pidfile() { + echo "$TEST_RUNDIR/chronyd.pid" +} + +get_logfile() { + echo "$TEST_LOGDIR/chronyd.log" +} + +get_cmdsocket() { + echo "$TEST_RUNDIR/chronyd.sock" +} + +# Find a free port in the 10000-20000 range (their use is racy) +get_free_port() { + local port + + while true; do + port=$((RANDOM % 10000 + 10000)) + $port_list_command | grep -q '^\(tcp\|udp\).*[:.]'"$port " && continue + break + done + + echo $port +} + +generate_chrony_conf() { + local ntpport cmdport + + ntpport=$(get_free_port) + cmdport=$(get_free_port) + + echo "0.0 10000" > "$TEST_LIBDIR/driftfile" + echo "1 MD5 abcdefghijklmnopq" > "$TEST_DIR/keys" + chown "$user:$(id -g "$user")" "$TEST_LIBDIR/driftfile" "$TEST_DIR/keys" + echo "0.0" > "$TEST_DIR/tempcomp" + + ( + echo "pidfile $(get_pidfile)" + echo "bindcmdaddress $(get_cmdsocket)" + echo "port $ntpport" + echo "cmdport $cmdport" + + echo "$extra_chronyd_directives" + + [ "$minimal_config" -ne 0 ] && exit 0 + + echo "allow" + echo "cmdallow" + echo "local" + + echo "server $server_name port $ntpport minpoll -6 maxpoll -6 $server_options" + + [ "$server" = "127.0.0.1" ] && echo "bindacqaddress $server" + echo "bindaddress 127.0.0.1" + echo "bindcmdaddress 127.0.0.1" + echo "dumpdir $TEST_RUNDIR" + echo "logdir $TEST_LOGDIR" + echo "log tempcomp rawmeasurements refclocks statistics tracking rtc" + echo "logbanner 0" + echo "smoothtime 100.0 0.001" + echo "leapsectz right/UTC" + echo "dscp 46" + + echo "include /dev/null" + echo "keyfile $TEST_DIR/keys" + echo "driftfile $TEST_LIBDIR/driftfile" + echo "tempcomp $TEST_DIR/tempcomp 0.1 0 0 0 0" + + ) > "$(get_conffile)" +} + +get_chronyd_options() { + [ "$clock_control" -eq 0 ] && echo "-x" + echo "-l $(get_logfile)" + echo "-f $(get_conffile)" + echo "-u $user" + echo "-F $TEST_SCFILTER" + echo "$extra_chronyd_options" +} + +# Start a chronyd instance +start_chronyd() { + local pid pidfile=$(get_pidfile) + + print_nondefaults + test_message 1 0 "starting chronyd" + + generate_chrony_conf + + trap stop_chronyd EXIT + + rm -f "$TEST_LOGDIR"/*.log + + $CHRONYD_WRAPPER "$chronyd" $(get_chronyd_options) > "$TEST_DIR/chronyd.out" 2>&1 + + [ $? -eq 0 ] && [ -f "$pidfile" ] && ps -p "$(cat "$pidfile")" > /dev/null && test_ok || test_error +} + +wait_for_sync() { + local prev_length + + test_message 1 0 "waiting for synchronization" + prev_length=$msg_length + + for i in $(seq 1 10); do + run_chronyc "ntpdata $server" > /dev/null 2>&1 || break + if check_chronyc_output "Total RX +: [1-9]" > /dev/null 2>&1; then + msg_length=$prev_length + test_ok + return + fi + sleep 1 + done + + msg_length=$prev_length + test_error +} + +# Stop the chronyd instance +stop_chronyd() { + local pid pidfile + + pidfile=$(get_pidfile) + [ -f "$pidfile" ] || return 0 + + pid=$(cat "$pidfile") + + test_message 1 0 "stopping chronyd" + + if ! kill "$pid" 2> /dev/null; then + test_error + return + fi + + # Wait for the process to terminate (we cannot use "wait") + while ps -p "$pid" > /dev/null; do + sleep 0.1 + done + + test_ok +} + +# Check chronyd log for expected and unexpected messages +check_chronyd_messages() { + local logfile=$(get_logfile) + + test_message 1 0 "checking chronyd messages" + + grep -q 'chronyd exiting' "$logfile" && \ + ([ "$clock_control" -eq 0 ] || ! grep -q 'Disabled control of system clock' "$logfile") && \ + ([ "$clock_control" -ne 0 ] || grep -q 'Disabled control of system clock' "$logfile") && \ + ([ "$minimal_config" -ne 0 ] || grep -q 'Frequency .* read from' "$logfile") && \ + grep -q 'chronyd exiting' "$logfile" && \ + ! grep -q 'Could not' "$logfile" && \ + ! grep -q 'Disabled command socket' "$logfile" && \ + test_ok || test_bad +} + +# Check the number of messages matching a pattern in a specified file +check_chronyd_message_count() { + local count pattern=$1 min=$2 max=$3 logfile=$(get_logfile) + + test_message 1 0 "checking message \"$pattern\"" + + count=$(grep "$pattern" "$(get_logfile)" | wc -l) + + [ "$min" -le "$count" ] && [ "$count" -le "$max" ] && test_ok || test_bad +} + +# Check the logs and dump file for measurements and a clock update +check_chronyd_files() { + test_message 1 0 "checking chronyd files" + + grep -q " $server .* 111 111 1110 " "$TEST_LOGDIR/measurements.log" && \ + [ -f "$TEST_LOGDIR/tempcomp.log" ] && [ "$(wc -l < "$TEST_LOGDIR/tempcomp.log")" -ge 2 ] && \ + test_ok || test_bad +} + +# Run a chronyc command +run_chronyc() { + local host=$chronyc_host options="-n -m" + + test_message 1 0 "running chronyc $([ -n "$host" ] && echo "@$host ")$*" + + if [ -z "$host" ]; then + host="$(get_cmdsocket)" + else + options="$options -p $(grep cmdport "$(get_conffile)" | awk '{print $2}')" + fi + + $CHRONYC_WRAPPER "$chronyc" -h "$host" $options "$@" > "$TEST_DIR/chronyc.out" && \ + test_ok || test_error +} + +# Compare chronyc output with specified pattern +check_chronyc_output() { + local pattern=$1 + + test_message 1 0 "checking chronyc output" + + [[ "$(cat "$TEST_DIR/chronyc.out")" =~ $pattern ]] && test_ok || test_bad +} diff --git a/test/unit/Makefile.in b/test/unit/Makefile.in new file mode 100644 index 0000000..9979840 --- /dev/null +++ b/test/unit/Makefile.in @@ -0,0 +1,48 @@ +TEST_WRAPPER = +CHRONY_SRCDIR = ../.. + +CC = @CC@ +CFLAGS = @CFLAGS@ +CPPFLAGS = -I$(CHRONY_SRCDIR) @CPPFLAGS@ +LDFLAGS = @LDFLAGS@ @LIBS@ @EXTRA_LIBS@ + +SHARED_OBJS = test.o + +TEST_OBJS := $(sort $(patsubst %.c,%.o,$(wildcard *.c))) +TESTS := $(patsubst %.o,%.test,$(filter-out $(SHARED_OBJS),$(TEST_OBJS))) + +CHRONYD_OBJS := $(patsubst %.o,$(CHRONY_SRCDIR)/%.o,$(filter-out main.o,\ + $(filter %.o,$(shell $(MAKE) -f $(CHRONY_SRCDIR)/Makefile \ + print-chronyd-objects NODEPS=1)))) + +all: $(TESTS) + +$(CHRONYD_OBJS): ; + +%.test: %.o $(SHARED_OBJS) $(CHRONYD_OBJS) + $(CC) $(CFLAGS) -o $@ $(filter-out $(CHRONY_SRCDIR)/$<,$^) $(LDFLAGS) + +%.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< + +check: $(TESTS) + @ret=0; \ + for t in $^; do \ + $(TEST_WRAPPER) ./$$t || ret=1; \ + done; \ + exit $$ret + +clean: + rm -f *.o *.gcda *.gcno core.* $(TESTS) + rm -rf .deps + +distclean: clean + rm -f Makefile + +.deps: + @mkdir .deps + +.deps/%.d: %.c | .deps + @$(CC) -MM $(CPPFLAGS) -MT '$(<:%.c=%.o) $@' $< -o $@ + +-include $(TEST_OBJS:%.o=.deps/%.d) diff --git a/test/unit/addrfilt.c b/test/unit/addrfilt.c new file mode 100644 index 0000000..b236073 --- /dev/null +++ b/test/unit/addrfilt.c @@ -0,0 +1,83 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <addrfilt.c> +#include <logging.h> +#include <util.h> +#include "test.h" + +void +test_unit(void) +{ + int i, j, sub, maxsub; + IPAddr ip; + ADF_AuthTable table; + + table = ADF_CreateTable(); + + for (i = 0; i < 100; i++) { + for (j = 0; j < 1000; j++) { + if (j % 2) { + maxsub = 32; + TST_GetRandomAddress(&ip, IPADDR_INET4, -1); + } else { + maxsub = 128; + TST_GetRandomAddress(&ip, IPADDR_INET6, -1); + } + + DEBUG_LOG("address %s", UTI_IPToString(&ip)); + + sub = random() % (maxsub + 1); + + TEST_CHECK(!ADF_IsAllowed(table, &ip)); + ADF_Allow(table, &ip, sub); + TEST_CHECK(ADF_IsAllowed(table, &ip)); + + if (sub < maxsub) { + TST_SwapAddressBit(&ip, sub); + TEST_CHECK(ADF_IsAllowed(table, &ip)); + } + + if (sub > 0) { + TST_SwapAddressBit(&ip, sub - 1); + TEST_CHECK(!ADF_IsAllowed(table, &ip)); + if (sub % 4 != 1) { + ADF_Deny(table, &ip, sub - 1); + TST_SwapAddressBit(&ip, sub - 1); + TEST_CHECK(!ADF_IsAllowed(table, &ip)); + } + } + + if (sub > 4) { + ADF_AllowAll(table, &ip, sub - 4); + TEST_CHECK(ADF_IsAllowed(table, &ip)); + } + + ADF_DenyAll(table, &ip, 0); + } + + ip.family = IPADDR_INET4; + ADF_DenyAll(table, &ip, 0); + ip.family = IPADDR_INET6; + ADF_DenyAll(table, &ip, 0); + } + + ADF_DestroyTable(table); +} diff --git a/test/unit/clientlog.c b/test/unit/clientlog.c new file mode 100644 index 0000000..f59e130 --- /dev/null +++ b/test/unit/clientlog.c @@ -0,0 +1,292 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2016, 2021 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#if defined(FEAT_NTP) || defined(FEAT_CMDMON) + +#include <clientlog.c> + +static uint64_t +get_random64(void) +{ + return ((uint64_t)random() << 40) ^ ((uint64_t)random() << 20) ^ random(); +} + +void +test_unit(void) +{ + uint64_t ts64, prev_first_ts64, prev_last_ts64, max_step; + uint32_t index2, prev_first, prev_size; + struct timespec ts, ts2; + int i, j, k, index, shift; + CLG_Service s; + NTP_int64 ntp_ts; + IPAddr ip; + char conf[][100] = { + "clientloglimit 20000", + "ratelimit interval 3 burst 4 leak 3", + "cmdratelimit interval 3 burst 4 leak 3", + "ntsratelimit interval 6 burst 8 leak 3", + }; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + LCL_Initialise(); + CLG_Initialise(); + + TEST_CHECK(ARR_GetSize(records) == 16); + + for (i = 0; i < 500; i++) { + DEBUG_LOG("iteration %d", i); + + ts.tv_sec = (time_t)random() & 0x0fffffff; + ts.tv_nsec = 0; + + for (j = 0; j < 1000; j++) { + TST_GetRandomAddress(&ip, IPADDR_UNSPEC, i % 8 ? -1 : i / 8 % 9); + DEBUG_LOG("address %s", UTI_IPToString(&ip)); + + s = random() % MAX_SERVICES; + index = CLG_LogServiceAccess(s, &ip, &ts); + TEST_CHECK(index >= 0); + CLG_LimitServiceRate(s, index); + + UTI_AddDoubleToTimespec(&ts, (1 << random() % 14) / 100.0, &ts); + } + } + + DEBUG_LOG("records %u", ARR_GetSize(records)); + TEST_CHECK(ARR_GetSize(records) == 128); + + s = CLG_NTP; + + for (i = j = 0; i < 10000; i++) { + ts.tv_sec += 1; + index = CLG_LogServiceAccess(s, &ip, &ts); + TEST_CHECK(index >= 0); + if (!CLG_LimitServiceRate(s, index)) + j++; + } + + DEBUG_LOG("requests %d responses %d", i, j); + TEST_CHECK(j * 4 < i && j * 6 > i); + + TEST_CHECK(!ntp_ts_map.timestamps); + + UTI_ZeroNtp64(&ntp_ts); + CLG_SaveNtpTimestamps(&ntp_ts, NULL); + TEST_CHECK(ntp_ts_map.timestamps); + TEST_CHECK(ntp_ts_map.first == 0); + TEST_CHECK(ntp_ts_map.size == 0); + TEST_CHECK(ntp_ts_map.max_size == 128); + TEST_CHECK(ARR_GetSize(ntp_ts_map.timestamps) == ntp_ts_map.max_size); + + TEST_CHECK(ntp_ts_map.max_size > NTPTS_INSERT_LIMIT); + + for (i = 0; i < 200; i++) { + DEBUG_LOG("iteration %d", i); + + max_step = (1ULL << (i % 50)); + ts64 = 0ULL - 100 * max_step; + + if (i > 150) + ntp_ts_map.max_size = 1U << (i % 8); + assert(ntp_ts_map.max_size <= 128); + ntp_ts_map.first = i % ntp_ts_map.max_size; + ntp_ts_map.size = 0; + ntp_ts_map.cached_rx_ts = 0ULL; + ntp_ts_map.slew_epoch = i * 400; + + for (j = 0; j < 500; j++) { + do { + ts64 += get_random64() % max_step + 1; + } while (ts64 == 0ULL); + + int64_to_ntp64(ts64, &ntp_ts); + + if (random() % 10) { + UTI_Ntp64ToTimespec(&ntp_ts, &ts); + UTI_AddDoubleToTimespec(&ts, TST_GetRandomDouble(-1.999, 1.999), &ts); + } else { + UTI_ZeroTimespec(&ts); + } + + CLG_SaveNtpTimestamps(&ntp_ts, + UTI_IsZeroTimespec(&ts) ? (random() % 2 ? &ts : NULL) : &ts); + + if (j < ntp_ts_map.max_size) { + TEST_CHECK(ntp_ts_map.size == j + 1); + TEST_CHECK(ntp_ts_map.first == i % ntp_ts_map.max_size); + } else { + TEST_CHECK(ntp_ts_map.size == ntp_ts_map.max_size); + TEST_CHECK(ntp_ts_map.first == (i + j + ntp_ts_map.size + 1) % ntp_ts_map.max_size); + } + TEST_CHECK(ntp_ts_map.cached_index == ntp_ts_map.size - 1); + TEST_CHECK(get_ntp_tss(ntp_ts_map.size - 1)->slew_epoch == ntp_ts_map.slew_epoch); + TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts2)); + TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0); + + for (k = random() % 4; k > 0; k--) { + index2 = random() % ntp_ts_map.size; + int64_to_ntp64(get_ntp_tss(index2)->rx_ts, &ntp_ts); + if (random() % 2) + TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts)); + + UTI_Ntp64ToTimespec(&ntp_ts, &ts); + UTI_AddDoubleToTimespec(&ts, TST_GetRandomDouble(-1.999, 1.999), &ts); + + ts2 = ts; + CLG_UndoNtpTxTimestampSlew(&ntp_ts, &ts); + if ((get_ntp_tss(index2)->slew_epoch + 1) % (1U << 16) != ntp_ts_map.slew_epoch) { + TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0); + } else { + TEST_CHECK(fabs(UTI_DiffTimespecsToDouble(&ts, &ts2) - ntp_ts_map.slew_offset) < + 1.0e-9); + } + + CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts); + + TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts2)); + TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0); + + if (random() % 2) { + uint16_t prev_epoch = ntp_ts_map.slew_epoch; + handle_slew(NULL, NULL, 0.0, TST_GetRandomDouble(-1.0e-5, 1.0e-5), + LCL_ChangeAdjust, NULL); + TEST_CHECK((prev_epoch + 1) % (1U << 16) == ntp_ts_map.slew_epoch); + } + + if (ntp_ts_map.size > 1) { + index = random() % (ntp_ts_map.size - 1); + if (get_ntp_tss(index)->rx_ts + 1 != get_ntp_tss(index + 1)->rx_ts) { + int64_to_ntp64(get_ntp_tss(index)->rx_ts + 1, &ntp_ts); + TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts)); + int64_to_ntp64(get_ntp_tss(index + 1)->rx_ts - 1, &ntp_ts); + TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts)); + CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts); + CLG_UndoNtpTxTimestampSlew(&ntp_ts, &ts); + } + } + + if (random() % 2) { + int64_to_ntp64(get_ntp_tss(0)->rx_ts - 1, &ntp_ts); + TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts)); + int64_to_ntp64(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts + 1, &ntp_ts); + TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts)); + CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts); + CLG_UndoNtpTxTimestampSlew(&ntp_ts, &ts); + } + } + } + + for (j = 0; j < 500; j++) { + shift = (i % 3) * 26; + + if (i % 7 == 0) { + while (ntp_ts_map.size < ntp_ts_map.max_size) { + ts64 += get_random64() >> (shift + 8); + int64_to_ntp64(ts64, &ntp_ts); + CLG_SaveNtpTimestamps(&ntp_ts, NULL); + if (ntp_ts_map.cached_index + NTPTS_INSERT_LIMIT < ntp_ts_map.size) + ts64 = get_ntp_tss(ntp_ts_map.size - 1)->rx_ts; + } + } + do { + if (ntp_ts_map.size > 1 && random() % 2) { + k = random() % (ntp_ts_map.size - 1); + ts64 = get_ntp_tss(k)->rx_ts + + (get_ntp_tss(k + 1)->rx_ts - get_ntp_tss(k)->rx_ts) / 2; + } else { + ts64 = get_random64() >> shift; + } + } while (ts64 == 0ULL); + + int64_to_ntp64(ts64, &ntp_ts); + + prev_first = ntp_ts_map.first; + prev_size = ntp_ts_map.size; + prev_first_ts64 = get_ntp_tss(0)->rx_ts; + prev_last_ts64 = get_ntp_tss(prev_size - 1)->rx_ts; + CLG_SaveNtpTimestamps(&ntp_ts, NULL); + + TEST_CHECK(find_ntp_rx_ts(ts64, &index2)); + + if (ntp_ts_map.size > 1) { + TEST_CHECK(ntp_ts_map.size > 0 && ntp_ts_map.size <= ntp_ts_map.max_size); + if (get_ntp_tss(index2)->flags & NTPTS_DISABLED) + continue; + + TEST_CHECK(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts - ts64 <= NTPTS_FUTURE_LIMIT); + + if ((int64_t)(prev_last_ts64 - ts64) <= NTPTS_FUTURE_LIMIT) { + TEST_CHECK(prev_size + 1 >= ntp_ts_map.size); + if (index2 + NTPTS_INSERT_LIMIT + 1 >= ntp_ts_map.size && + !(index2 == 0 && NTPTS_INSERT_LIMIT < ntp_ts_map.max_size && + ((NTPTS_INSERT_LIMIT == prev_size && (int64_t)(ts64 - prev_first_ts64) > 0) || + (NTPTS_INSERT_LIMIT + 1 == prev_size && (int64_t)(ts64 - prev_first_ts64) < 0)))) + TEST_CHECK((prev_first + prev_size + 1) % ntp_ts_map.max_size == + (ntp_ts_map.first + ntp_ts_map.size) % ntp_ts_map.max_size); + else + TEST_CHECK(prev_first + prev_size == ntp_ts_map.first + ntp_ts_map.size); + } + + TEST_CHECK((int64_t)(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts - + get_ntp_tss(0)->rx_ts) > 0); + for (k = 0; k + 1 < ntp_ts_map.size; k++) + TEST_CHECK((int64_t)(get_ntp_tss(k + 1)->rx_ts - get_ntp_tss(k)->rx_ts) > 0); + } + + if (random() % 10 == 0) { + CLG_DisableNtpTimestamps(&ntp_ts); + TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts)); + } + + for (k = random() % 10; k > 0; k--) { + ts64 = get_random64() >> shift; + int64_to_ntp64(ts64, &ntp_ts); + CLG_GetNtpTxTimestamp(&ntp_ts, &ts); + } + } + + if (random() % 2) { + handle_slew(NULL, NULL, 0.0, TST_GetRandomDouble(-1.0e9, 1.0e9), + LCL_ChangeUnknownStep, NULL); + TEST_CHECK(ntp_ts_map.size == 0); + TEST_CHECK(ntp_ts_map.cached_rx_ts == 0ULL); + TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts)); + CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts); + } + } + + CLG_Finalise(); + LCL_Finalise(); + CNF_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/cmac.c b/test/unit/cmac.c new file mode 100644 index 0000000..be4ffbb --- /dev/null +++ b/test/unit/cmac.c @@ -0,0 +1,109 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2019 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include <sysincl.h> +#include <cmac.h> +#include <logging.h> +#include <util.h> +#include "test.h" + +#define MAX_KEY_LENGTH 64 +#define MAX_HASH_LENGTH 64 + +struct cmac_test { + const char *name; + const unsigned char key[MAX_KEY_LENGTH]; + int key_length; + const unsigned char hash[MAX_HASH_LENGTH]; + int hash_length; +}; + +void +test_unit(void) +{ + unsigned char data[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + unsigned char hash[MAX_HASH_LENGTH]; + struct cmac_test tests[] = { + { "AES128", "\xfc\x24\x97\x1b\x52\x66\xdc\x46\xef\xe0\xe8\x08\x46\x89\xb6\x88", 16, + "\xaf\x3c\xfe\xc2\x66\x71\x08\x04\xd4\xaf\x5b\x16\x2b\x11\xf4\x85", 16 }, + { "AES256", "\x14\x02\x8e\x7d\x17\x3c\x2f\x4e\x17\x0f\x37\x96\xc3\x2c\xc5\x99" + "\x18\xdd\x55\x23\xb7\xd7\x9b\xc5\x76\x36\x88\x3f\xc5\x82\xb5\x83", 32, + "\xfe\xf7\x94\x96\x14\x04\x11\x0b\x87\xe4\xd4\x3f\x81\xb3\xb2\x2d", 16 }, + { "", "", 0, "", 0 } + }; + + CMC_Algorithm algorithm; + CMC_Instance inst; + int i, j, length; + +#ifndef HAVE_CMAC + TEST_REQUIRE(0); +#endif + + TEST_CHECK(CMC_INVALID == 0); + + for (i = 0; tests[i].name[0] != '\0'; i++) { + algorithm = UTI_CmacNameToAlgorithm(tests[i].name); + TEST_CHECK(algorithm != 0); + TEST_CHECK(CMC_GetKeyLength(algorithm) == tests[i].key_length); + + DEBUG_LOG("testing %s", tests[i].name); + + for (j = -1; j <= 128; j++) { + if (j == tests[i].key_length) + continue; + TEST_CHECK(!CMC_CreateInstance(algorithm, tests[i].key, j)); + } + + inst = CMC_CreateInstance(algorithm, tests[i].key, tests[i].key_length); + TEST_CHECK(inst); + + TEST_CHECK(!CMC_CreateInstance(0, tests[i].key, tests[i].key_length)); + + TEST_CHECK(CMC_Hash(inst, data, -1, hash, sizeof (hash)) == 0); + TEST_CHECK(CMC_Hash(inst, data, sizeof (data) - 1, hash, -1) == 0); + + for (j = 0; j <= sizeof (hash); j++) { + memset(hash, 0, sizeof (hash)); + length = CMC_Hash(inst, data, sizeof (data) - 1, hash, j); + +#if 0 + for (int k = 0; k < length; k++) + printf("\\x%02x", hash[k]); + printf("\n"); +#endif + + if (j >= tests[i].hash_length) + TEST_CHECK(length == tests[i].hash_length); + else + TEST_CHECK(length == j); + + TEST_CHECK(!memcmp(hash, tests[i].hash, length)); + } + + for (j = 0; j < sizeof (data); j++) { + length = CMC_Hash(inst, data, j, hash, sizeof (hash)); + TEST_CHECK(length == tests[i].hash_length); + } + + CMC_DestroyInstance(inst); + } +} diff --git a/test/unit/hash.c b/test/unit/hash.c new file mode 100644 index 0000000..2f9ffef --- /dev/null +++ b/test/unit/hash.c @@ -0,0 +1,131 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include <sysincl.h> +#include <hash.h> +#include <logging.h> +#include <util.h> +#include "test.h" + +struct hash_test { + const char *name; + const unsigned char out[MAX_HASH_LENGTH]; + int length; +}; + +void +test_unit(void) +{ + unsigned char data1[] = "abcdefghijklmnopqrstuvwxyz"; + unsigned char data2[] = "12345678910"; + unsigned char out[MAX_HASH_LENGTH]; + struct hash_test tests[] = { + { "MD5-NC", "\xfc\x24\x97\x1b\x52\x66\xdc\x46\xef\xe0\xe8\x08\x46\x89\xb6\x88", 16 }, + { "MD5", "\xfc\x24\x97\x1b\x52\x66\xdc\x46\xef\xe0\xe8\x08\x46\x89\xb6\x88", 16 }, + { "SHA1", "\xd8\x85\xb3\x86\xce\xea\x93\xeb\x92\xcd\x7b\x94\xb9\x8d\xc2\x8e" + "\x3e\x31\x13\xdd", 20}, + { "SHA256", "\x0e\x35\x14\xe7\x15\x7a\x1d\xdd\xea\x11\x78\xd3\x41\xf6\xb9\x3e" + "\xa0\x42\x96\x73\x3c\x54\x74\x0b\xfa\x6b\x9e\x29\x59\xad\x69\xd3", 32 }, + { "SHA384", "\x2c\xeb\xbd\x4d\x95\xed\xad\x03\xed\x80\xc4\xf3\xa6\x10\x21\xde" + "\x40\x69\x54\xed\x42\x70\xb8\x95\xb0\x6f\x01\x1d\x04\xdf\x57\xbc" + "\x1d\xb5\x85\xbf\x4f\x03\x88\xd5\x83\x93\xbc\x81\x90\xb0\xa9\x9b", 48 }, + { "SHA512", "\x20\xba\xec\xcb\x68\x98\x33\x5b\x70\x26\x63\x13\xe2\xf7\x0e\x67" + "\x08\xf3\x77\x4f\xbd\xeb\xc4\xa8\xc5\x94\xe2\x39\x40\x7e\xed\x0b" + "\x69\x0e\x18\xa5\xa2\x03\x73\xe7\x1d\x20\x7d\x3f\xc8\x70\x2d\x64" + "\x9e\x89\x6d\x20\x6a\x4a\x5a\x46\xe7\x4f\x2c\xf9\x0f\x0a\x54\xdc", 64 }, + { "SHA3-224", "\x3b\xa2\x22\x28\xdd\x26\x18\xec\x3b\xb9\x25\x39\x5e\xbd\x94\x25" + "\xd4\x20\x8a\x76\x76\xc0\x3c\x5d\x9e\x0a\x06\x46", 28}, + { "SHA3-256", "\x26\xd1\x19\xb2\xc1\x64\xc8\xb8\x10\xd8\xa8\x1c\xb6\xa4\x0d\x29" + "\x09\xc9\x8e\x2e\x2d\xde\x7a\x74\x8c\x43\x70\xb8\xaa\x0f\x09\x17", 32 }, + { "SHA3-384", "\x6a\x64\xb9\x89\x08\x29\xd0\xa7\x4b\x84\xba\xa6\x65\xf5\xe7\x54" + "\xe2\x18\x12\xc3\x63\x34\xc6\xba\x26\xf5\x6e\x99\xe2\x54\xcc\x9d" + "\x01\x10\x9d\xee\x35\x38\x04\x83\xe5\x71\x70\xd8\xc8\x99\x96\xd8", 48 }, + { "SHA3-512", "\xa8\xe3\x2b\x65\x1f\x87\x90\x73\x19\xc8\xa0\x3f\xe3\x85\x60\x3c" + "\x39\xfc\xcb\xc1\x29\xe1\x23\x7d\x8b\x56\x54\xe3\x08\x9d\xf9\x74" + "\x78\x69\x2e\x3c\x7e\x51\x1e\x9d\xab\x09\xbe\xe7\x6b\x1a\xa1\x22" + "\x93\xb1\x2b\x82\x9d\x1e\xcf\xa8\x99\xc5\xec\x7b\x1d\x89\x07\x2b", 64 }, + { "TIGER", "\x1c\xcd\x68\x74\xca\xd6\xd5\x17\xba\x3e\x82\xaf\xbd\x70\xdc\x66" + "\x99\xaa\xae\x16\x72\x59\xd1\x64", 24}, + { "WHIRLPOOL", "\xe3\xcd\xe6\xbf\xe1\x8c\xe4\x4d\xc8\xb4\xa5\x7c\x36\x8d\xc8\x8a" + "\x8b\xad\x52\x24\xc0\x4e\x99\x5b\x7e\x86\x94\x2d\x10\x56\x12\xa3" + "\x29\x2a\x65\x0f\x9e\x07\xbc\x15\x21\x14\xe6\x07\xfc\xe6\xb9\x2f" + "\x13\xe2\x57\xe9\x0a\xb0\xd2\xf4\xa3\x20\x36\x9c\x88\x92\x8e\xc9", 64 }, + { "", "", 0 } + }; + + HSH_Algorithm algorithm; + int i, j, hash_id, length; + + TEST_CHECK(HSH_INVALID == 0); + + for (i = 0; tests[i].name[0] != '\0'; i++) { + algorithm = UTI_HashNameToAlgorithm(tests[i].name); + if (strcmp(tests[i].name, "MD5-NC") == 0) { + TEST_CHECK(algorithm == 0); + algorithm = HSH_MD5_NONCRYPTO; + } else { + TEST_CHECK(algorithm != 0); + } + hash_id = HSH_GetHashId(algorithm); + if (hash_id < 0) { + TEST_CHECK(algorithm != HSH_MD5_NONCRYPTO); + TEST_CHECK(algorithm != HSH_MD5); +#ifdef FEAT_SECHASH + TEST_CHECK(algorithm != HSH_SHA1); + TEST_CHECK(algorithm != HSH_SHA256); + TEST_CHECK(algorithm != HSH_SHA384); + TEST_CHECK(algorithm != HSH_SHA512); +#endif + continue; + } + + DEBUG_LOG("testing %s", tests[i].name); + + TEST_CHECK(HSH_Hash(hash_id, data1, -1, NULL, 0, out, sizeof (out)) == 0); + TEST_CHECK(HSH_Hash(hash_id, data1, sizeof (data1) - 1, data2, -1, out, sizeof (out)) == 0); + TEST_CHECK(HSH_Hash(hash_id, data1, sizeof (data1) - 1, NULL, 0, out, -1) == 0); + + for (j = 0; j <= sizeof (out); j++) { + TEST_CHECK(HSH_GetHashId(algorithm) == hash_id); + TEST_CHECK(HSH_GetHashId(0) < 0); + + memset(out, 0, sizeof (out)); + length = HSH_Hash(hash_id, data1, sizeof (data1) - 1, data2, sizeof (data2) - 1, + out, j); + + if (j >= tests[i].length) + TEST_CHECK(length == tests[i].length); + else + TEST_CHECK(length == j); + + TEST_CHECK(!memcmp(out, tests[i].out, length)); + } + + for (j = 0; j < 10000; j++) { + length = HSH_Hash(hash_id, data1, random() % sizeof (data1), + random() % 2 ? data2 : NULL, random() % sizeof (data2), + out, sizeof (out)); + TEST_CHECK(length == tests[i].length); + } + } + + HSH_Finalise(); +} diff --git a/test/unit/hwclock.c b/test/unit/hwclock.c new file mode 100644 index 0000000..79c0879 --- /dev/null +++ b/test/unit/hwclock.c @@ -0,0 +1,117 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2016-2018, 2022 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#if defined(FEAT_PHC) || defined(HAVE_LINUX_TIMESTAMPING) + +#include <hwclock.c> + +#define MAX_READINGS 20 + +void +test_unit(void) +{ + struct timespec start_hw_ts, start_local_ts, hw_ts, local_ts, ts; + struct timespec readings[MAX_READINGS][3]; + HCL_Instance clock; + double freq, jitter, interval, dj, err, sum; + int i, j, k, l, new_sample, n_readings, count; + + LCL_Initialise(); + TST_RegisterDummyDrivers(); + + for (i = 1; i <= 8; i++) { + clock = HCL_CreateInstance(random() % (1 << i), 1 << i, 1.0, 1e-9); + + for (j = 0, count = 0, sum = 0.0; j < 100; j++) { + UTI_ZeroTimespec(&start_hw_ts); + UTI_ZeroTimespec(&start_local_ts); + UTI_AddDoubleToTimespec(&start_hw_ts, TST_GetRandomDouble(0.0, 1e9), &start_hw_ts); + UTI_AddDoubleToTimespec(&start_local_ts, TST_GetRandomDouble(0.0, 1e9), &start_local_ts); + + DEBUG_LOG("iteration %d", j); + + freq = TST_GetRandomDouble(0.9, 1.1); + jitter = TST_GetRandomDouble(10.0e-9, 1000.0e-9); + interval = TST_GetRandomDouble(0.1, 10.0); + + clock->n_samples = 0; + clock->valid_coefs = 0; + QNT_Reset(clock->delay_quants); + + new_sample = 0; + + for (k = 0; k < 100; k++) { + UTI_AddDoubleToTimespec(&start_hw_ts, k * interval * freq, &hw_ts); + UTI_AddDoubleToTimespec(&start_local_ts, k * interval, &local_ts); + if (HCL_CookTime(clock, &hw_ts, &ts, NULL) && new_sample) { + dj = fabs(UTI_DiffTimespecsToDouble(&ts, &local_ts) / jitter); + DEBUG_LOG("delta/jitter %f", dj); + if (clock->n_samples >= clock->max_samples / 2) + sum += dj, count++; + TEST_CHECK(clock->n_samples < 4 || dj <= 4.0); + TEST_CHECK(clock->n_samples < 8 || dj <= 3.0); + } + + UTI_AddDoubleToTimespec(&start_hw_ts, k * interval * freq + TST_GetRandomDouble(-jitter, jitter), &hw_ts); + + if (HCL_NeedsNewSample(clock, &local_ts)) { + n_readings = random() % MAX_READINGS + 1; + for (l = 0; l < n_readings; l++) { + UTI_AddDoubleToTimespec(&local_ts, -TST_GetRandomDouble(0.0, jitter / 10.0), &readings[l][0]); + readings[l][1] = hw_ts; + UTI_AddDoubleToTimespec(&local_ts, TST_GetRandomDouble(0.0, jitter / 10.0), &readings[l][2]); + } + + UTI_ZeroTimespec(&hw_ts); + UTI_ZeroTimespec(&local_ts); + if (HCL_ProcessReadings(clock, n_readings, readings, &hw_ts, &local_ts, &err)) { + HCL_AccumulateSample(clock, &hw_ts, &local_ts, 2.0 * jitter); + new_sample = 1; + } else { + new_sample = 0; + } + } + + TEST_CHECK(clock->valid_coefs == (clock->n_samples >= 2)); + + if (!clock->valid_coefs) + continue; + + TEST_CHECK(fabs(clock->offset) <= 2.0 * jitter); + } + } + + TEST_CHECK(sum / count < 2.4 / sqrt(clock->max_samples)); + + HCL_DestroyInstance(clock); + } + + LCL_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/keys.c b/test/unit/keys.c new file mode 100644 index 0000000..aa5e649 --- /dev/null +++ b/test/unit/keys.c @@ -0,0 +1,173 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2017 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#if defined(FEAT_NTP) || defined(FEAT_CMDMON) + +#include <keys.c> + +#define KEYS 100 +#define KEYFILE "keys.test-keys" + +static +uint32_t write_random_key(FILE *f) +{ + const char *type, *prefix; + char key[128]; + uint32_t id; + int i, length; + + length = random() % sizeof (key) + 1; + length = MAX(length, 4); + prefix = random() % 2 ? "HEX:" : ""; + + switch (random() % 8) { +#ifdef FEAT_SECHASH + case 0: + type = "SHA1"; + break; + case 1: + type = "SHA256"; + break; + case 2: + type = "SHA384"; + break; + case 3: + type = "SHA512"; + break; +#endif +#ifdef HAVE_CMAC + case 4: + type = "AES128"; + length = prefix[0] == '\0' ? 8 : 16; + break; + case 5: + type = "AES256"; + length = prefix[0] == '\0' ? 16 : 32; + break; +#endif + case 6: + type = "MD5"; + break; + default: + type = ""; + } + + UTI_GetRandomBytes(&id, sizeof (id)); + UTI_GetRandomBytes(key, length); + + fprintf(f, "%u %s %s", id, type, prefix); + for (i = 0; i < length; i++) + fprintf(f, "%02hhX", key[i]); + fprintf(f, "\n"); + + return id; +} + +static void +generate_key_file(const char *name, uint32_t *keys) +{ + FILE *f; + int i; + + f = fopen(name, "w"); + TEST_CHECK(f); + for (i = 0; i < KEYS; i++) + keys[i] = write_random_key(f); + fclose(f); +} + +void +test_unit(void) +{ + int i, j, data_len, auth_len, type, bits; + uint32_t keys[KEYS], key; + unsigned char data[100], auth[MAX_HASH_LENGTH]; + char conf[][100] = { + "keyfile "KEYFILE + }; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + generate_key_file(KEYFILE, keys); + KEY_Initialise(); + + for (i = 0; i < 100; i++) { + DEBUG_LOG("iteration %d", i); + + if (i) { + generate_key_file(KEYFILE, keys); + KEY_Reload(); + } + + UTI_GetRandomBytes(data, sizeof (data)); + + for (j = 0; j < KEYS; j++) { + TEST_CHECK(KEY_KeyKnown(keys[j])); + TEST_CHECK(KEY_GetAuthLength(keys[j]) >= 16); + + data_len = random() % (sizeof (data) + 1); + auth_len = KEY_GenerateAuth(keys[j], data, data_len, auth, sizeof (auth)); + TEST_CHECK(auth_len >= 16); + + TEST_CHECK(KEY_CheckAuth(keys[j], data, data_len, auth, auth_len, auth_len)); + + if (j > 0 && keys[j - 1] != keys[j]) + TEST_CHECK(!KEY_CheckAuth(keys[j - 1], data, data_len, auth, auth_len, auth_len)); + + auth_len = random() % auth_len + 1; + if (auth_len < MAX_HASH_LENGTH) + auth[auth_len]++; + TEST_CHECK(KEY_CheckAuth(keys[j], data, data_len, auth, auth_len, auth_len)); + + auth[auth_len - 1]++; + TEST_CHECK(!KEY_CheckAuth(keys[j], data, data_len, auth, auth_len, auth_len)); + + TEST_CHECK(KEY_GetKeyInfo(keys[j], &type, &bits)); + TEST_CHECK(type > 0 && bits > 0); + } + + for (j = 0; j < 1000; j++) { + UTI_GetRandomBytes(&key, sizeof (key)); + if (KEY_KeyKnown(key)) + continue; + TEST_CHECK(!KEY_GetKeyInfo(key, &type, &bits)); + TEST_CHECK(!KEY_GenerateAuth(key, data, data_len, auth, sizeof (auth))); + TEST_CHECK(!KEY_CheckAuth(key, data, data_len, auth, auth_len, auth_len)); + } + } + + unlink(KEYFILE); + + KEY_Finalise(); + CNF_Finalise(); + HSH_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/ntp_auth.c b/test/unit/ntp_auth.c new file mode 100644 index 0000000..5f2a9bc --- /dev/null +++ b/test/unit/ntp_auth.c @@ -0,0 +1,289 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include <sysincl.h> +#include <conf.h> +#include <keys.h> +#include <local.h> +#include <ntp_ext.h> +#include <ntp_signd.h> +#include <nts_ntp_server.h> +#include <socket.h> +#include "test.h" + +#ifdef FEAT_NTP + +#include <ntp_auth.c> + +static void +prepare_packet(NTP_AuthMode auth_mode, NTP_Packet *packet, NTP_PacketInfo *info, int req) +{ + unsigned char buf[64]; + int i, version; + NTP_Mode mode; + + switch (auth_mode) { + case NTP_AUTH_MSSNTP: + case NTP_AUTH_MSSNTP_EXT: + version = 3; + mode = random() % 2 ? (req ? MODE_CLIENT : MODE_SERVER) : + (req ? MODE_ACTIVE : MODE_PASSIVE); + break; + case NTP_AUTH_NTS: + version = 4; + mode = req ? MODE_CLIENT : MODE_SERVER; + break; + default: + version = 3 + random() % 2; + mode = random() % 2 ? (req ? MODE_CLIENT : MODE_SERVER) : + (req ? MODE_ACTIVE : MODE_PASSIVE); + break; + } + + memset(packet, 0, sizeof (*packet)); + memset(info, 0, sizeof (*info)); + packet->lvm = NTP_LVM(LEAP_Normal, version, mode); + info->length = NTP_HEADER_LENGTH; + info->version = version; + info->mode = mode; + + if (version == 4) { + memset(buf, 0, sizeof (buf)); + for (i = random() % 5; i > 0; i--) + TEST_CHECK(NEF_AddField(packet, info, 0, buf, sizeof (buf))); + } +} + +static void +add_dummy_auth(NTP_AuthMode auth_mode, uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info) +{ + unsigned char buf[64]; + int len, fill; + + info->auth.mode = auth_mode; + + switch (auth_mode) { + case NTP_AUTH_NONE: + break; + case NTP_AUTH_SYMMETRIC: + case NTP_AUTH_MSSNTP: + case NTP_AUTH_MSSNTP_EXT: + switch (auth_mode) { + case NTP_AUTH_SYMMETRIC: + len = 16 + random() % 2 * 4; + fill = 1 + random() % 255; + break; + case NTP_AUTH_MSSNTP: + len = 16; + fill = 0; + break; + case NTP_AUTH_MSSNTP_EXT: + len = 68; + fill = 0; + break; + default: + assert(0); + } + + assert(info->length + 4 + len <= sizeof (*packet)); + + *(uint32_t *)((unsigned char *)packet + info->length) = htonl(key_id); + info->auth.mac.key_id = key_id; + info->length += 4; + + memset((unsigned char *)packet + info->length, fill, len); + info->length += len; + break; + case NTP_AUTH_NTS: + memset(buf, 0, sizeof (buf)); + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_AUTH_AND_EEF, buf, sizeof (buf))); + break; + default: + assert(0); + } +} + +void +test_unit(void) +{ + int i, j, can_auth_req, can_auth_res; + NTP_PacketInfo req_info, res_info; + NTP_Packet req, res; + NAU_Instance inst; + RPT_AuthReport report; + NTP_AuthMode mode; + IPSockAddr nts_addr; + uint32_t key_id, kod; + char conf[][100] = { + "keyfile ntp_core.keys" + }; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + LCL_Initialise(); + KEY_Initialise(); + NSD_Initialise(); + NNS_Initialise(); + + SCK_GetAnyLocalIPAddress(IPADDR_INET4, &nts_addr.ip_addr); + nts_addr.port = 0; + + for (i = 0; i < 1000; i++) { + key_id = INACTIVE_AUTHKEY; + + switch (i % 5) { + case 0: + inst = NAU_CreateNoneInstance(); + TEST_CHECK(!NAU_IsAuthEnabled(inst)); + TEST_CHECK(NAU_GetSuggestedNtpVersion(inst) == 4); + mode = NTP_AUTH_NONE; + can_auth_req = 1; + can_auth_res = 1; + break; + case 1: + key_id = random() % 7 + 2; + inst = NAU_CreateSymmetricInstance(key_id); + TEST_CHECK(NAU_IsAuthEnabled(inst)); + TEST_CHECK(NAU_GetSuggestedNtpVersion(inst) == + (KEY_KeyKnown(inst->key_id) && KEY_GetAuthLength(inst->key_id) > 20 ? 3 : 4)); + mode = NTP_AUTH_SYMMETRIC; + can_auth_req = KEY_KeyKnown(key_id); + can_auth_res = can_auth_req; + break; + case 2: + inst = NAU_CreateNtsInstance(&nts_addr, "test", 0, 0); + TEST_CHECK(NAU_IsAuthEnabled(inst)); + TEST_CHECK(NAU_GetSuggestedNtpVersion(inst) == 4); + mode = NTP_AUTH_NTS; + can_auth_req = 0; + can_auth_res = 0; + break; + case 3: + key_id = 1 + random() % 100; + inst = NULL; + mode = NTP_AUTH_MSSNTP; + can_auth_req = 1; + can_auth_res = 0; + break; + case 4: + key_id = 1 + random() % 100; + inst = NULL; + mode = NTP_AUTH_MSSNTP_EXT; + can_auth_req = 0; + can_auth_res = 0; + break; + default: + assert(0); + } + + DEBUG_LOG("iteration %d auth=%d key_id=%d", i, (int)mode, (int)key_id); + + prepare_packet(mode, &req, &req_info, 1); + + if (inst) { + TEST_CHECK(inst->mode == mode); + TEST_CHECK(inst->key_id == key_id); + + NAU_DumpData(inst); + NAU_GetReport(inst, &report); + if (random() % 2) + NAU_ChangeAddress(inst, &nts_addr.ip_addr); + + if (inst->mode == NTP_AUTH_NTS) { + for (j = random() % 5; j > 0; j--) +#ifdef FEAT_NTS + TEST_CHECK(!NAU_PrepareRequestAuth(inst)); +#else + TEST_CHECK(NAU_PrepareRequestAuth(inst)); +#endif + TEST_CHECK(!NAU_GenerateRequestAuth(inst, &req, &req_info)); + } else if (can_auth_req) { + TEST_CHECK(NAU_PrepareRequestAuth(inst)); + TEST_CHECK(NAU_GenerateRequestAuth(inst, &req, &req_info)); + } else { + TEST_CHECK(NAU_PrepareRequestAuth(inst)); + TEST_CHECK(!NAU_GenerateRequestAuth(inst, &req, &req_info)); + } + } + + if (!inst || !can_auth_req) + add_dummy_auth(mode, key_id, &req, &req_info); + + assert(req_info.auth.mode == mode); + assert(req_info.auth.mac.key_id == key_id); + + kod = 1; + TEST_CHECK(NAU_CheckRequestAuth(&req, &req_info, &kod) == can_auth_req); + TEST_CHECK(kod == 0); + + if (inst) { + for (j = NTP_AUTH_NONE; j <= NTP_AUTH_NTS; j++) { + if (j == mode && j == NTP_AUTH_NONE) + continue; + + prepare_packet(j, &res, &res_info, 0); + add_dummy_auth(j, key_id ? key_id : 1, &res, &res_info); + + TEST_CHECK(res_info.auth.mode == j); + TEST_CHECK(!NAU_CheckResponseAuth(inst, &res, &res_info)); + } + } + + prepare_packet(mode, &res, &res_info, 0); + TEST_CHECK(NAU_GenerateResponseAuth(&req, &req_info, &res, &res_info, NULL, NULL, kod) == + can_auth_res); + if (!can_auth_res) + add_dummy_auth(mode, key_id, &res, &res_info); + + assert(res_info.auth.mode == mode); + assert(res_info.auth.mac.key_id == key_id); + + if (inst) { + if (mode == NTP_AUTH_SYMMETRIC) { + res_info.auth.mac.key_id ^= 1; + TEST_CHECK(!NAU_CheckResponseAuth(inst, &res, &res_info)); + res_info.auth.mac.key_id ^= 1; + } + + TEST_CHECK(NAU_CheckResponseAuth(inst, &res, &res_info) == can_auth_res); + + NAU_GetReport(inst, &report); + NAU_DestroyInstance(inst); + } + } + + NNS_Finalise(); + NSD_Finalise(); + KEY_Finalise(); + LCL_Finalise(); + CNF_Finalise(); + HSH_Finalise(); +} + +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/ntp_core.c b/test/unit/ntp_core.c new file mode 100644 index 0000000..875731e --- /dev/null +++ b/test/unit/ntp_core.c @@ -0,0 +1,623 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2017-2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include <sysincl.h> +#include <cmdparse.h> +#include <conf.h> +#include <keys.h> +#include <ntp_ext.h> +#include <ntp_io.h> +#include <sched.h> +#include <local.h> +#include "test.h" + +#ifdef FEAT_NTP + +static struct timespec current_time; +static NTP_Packet req_buffer, res_buffer; +static int req_length, res_length; + +#define NIO_OpenServerSocket(addr) ((addr)->ip_addr.family != IPADDR_UNSPEC ? 100 : 0) +#define NIO_CloseServerSocket(fd) assert(fd == 100) +#define NIO_OpenClientSocket(addr) ((addr)->ip_addr.family != IPADDR_UNSPEC ? 101 : 0) +#define NIO_CloseClientSocket(fd) assert(fd == 101) +#define NIO_IsServerSocket(fd) (fd == 100) +#define NIO_IsServerSocketOpen() 1 +#define NIO_SendPacket(msg, to, from, len, process_tx) (memcpy(&req_buffer, msg, len), req_length = len, 1) +#define SCH_AddTimeoutByDelay(delay, handler, arg) (1 ? 102 : (handler(arg), 1)) +#define SCH_AddTimeoutInClass(delay, separation, randomness, class, handler, arg) \ + add_timeout_in_class(delay, separation, randomness, class, handler, arg) +#define SCH_RemoveTimeout(id) assert(!id || id == 102) +#define LCL_ReadRawTime(ts) (*ts = current_time) +#define LCL_ReadCookedTime(ts, err) do {double *p = err; *ts = current_time; if (p) *p = 0.0;} while (0) +#define LCL_GetSysPrecisionAsLog() (random() % 10 - 30) +#define SRC_UpdateReachability(inst, reach) +#define SRC_ResetReachability(inst) + +static SCH_TimeoutID +add_timeout_in_class(double min_delay, double separation, double randomness, + SCH_TimeoutClass class, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg) +{ + return 102; +} + +#include <ntp_core.c> + +static void +advance_time(double x) +{ + UTI_AddDoubleToTimespec(¤t_time, x, ¤t_time); +} + +static uint32_t +get_random_key_id(void) +{ + uint32_t id; + + do { + id = random() % 8 + 2; + } while (!KEY_KeyKnown(id)); + + return id; +} + +static void +send_request(NCR_Instance inst) +{ + NTP_Local_Address local_addr; + NTP_Local_Timestamp local_ts; + uint32_t prev_tx_count; + + prev_tx_count = inst->report.total_tx_count; + + transmit_timeout(inst); + TEST_CHECK(!inst->valid_rx); + TEST_CHECK(prev_tx_count + 1 == inst->report.total_tx_count); + + advance_time(1e-5); + + if (random() % 2) { + local_addr.ip_addr.family = IPADDR_UNSPEC; + local_addr.if_index = INVALID_IF_INDEX; + local_addr.sock_fd = 101; + local_ts.ts = current_time; + local_ts.err = 0.0; + local_ts.source = NTP_TS_KERNEL; + + NCR_ProcessTxKnown(inst, &local_addr, &local_ts, &req_buffer, req_length); + } +} + +static void +process_request(NTP_Remote_Address *remote_addr) +{ + NTP_Local_Address local_addr; + NTP_Local_Timestamp local_ts; + + local_addr.ip_addr.family = IPADDR_UNSPEC; + local_addr.if_index = INVALID_IF_INDEX; + local_addr.sock_fd = 100; + local_ts.ts = current_time; + local_ts.err = 0.0; + local_ts.source = NTP_TS_KERNEL; + + res_length = 0; + NCR_ProcessRxUnknown(remote_addr, &local_addr, &local_ts, + &req_buffer, req_length); + res_length = req_length; + res_buffer = req_buffer; + + advance_time(1e-5); + + if (random() % 2) { + local_ts.ts = current_time; + NCR_ProcessTxUnknown(remote_addr, &local_addr, &local_ts, + &res_buffer, res_length); + } +} + +static void +send_response(int interleaved, int authenticated, int allow_update, int valid_ts, int valid_auth) +{ + NTP_Packet *req, *res; + uint32_t key_id = 0; + int i, auth_len = 0, ef_len, efs; + + req = &req_buffer; + res = &res_buffer; + + TEST_CHECK(req_length >= NTP_HEADER_LENGTH); + + res->lvm = NTP_LVM(LEAP_Normal, NTP_LVM_TO_VERSION(req->lvm), + NTP_LVM_TO_MODE(req->lvm) == MODE_CLIENT ? MODE_SERVER : MODE_ACTIVE); + res->stratum = 1; + res->poll = req->poll; + res->precision = -20; + res->root_delay = UTI_DoubleToNtp32(0.1); + res->root_dispersion = UTI_DoubleToNtp32(0.1); + res->reference_id = 0; + UTI_ZeroNtp64(&res->reference_ts); + res->originate_ts = interleaved ? req->receive_ts : req->transmit_ts; + + advance_time(TST_GetRandomDouble(1e-4, 1e-2)); + UTI_TimespecToNtp64(¤t_time, &res->receive_ts, NULL); + advance_time(TST_GetRandomDouble(-1e-4, 1e-3)); + UTI_TimespecToNtp64(¤t_time, &res->transmit_ts, NULL); + advance_time(TST_GetRandomDouble(1e-4, 1e-2)); + + if (!valid_ts) { + switch (random() % (allow_update ? 4 : 5)) { + case 0: + res->originate_ts.hi = random(); + break; + case 1: + res->originate_ts.lo = random(); + break; + case 2: + UTI_ZeroNtp64(&res->originate_ts); + break; + case 3: + UTI_ZeroNtp64(&res->receive_ts); + break; + case 4: + UTI_ZeroNtp64(&res->transmit_ts); + break; + default: + assert(0); + } + } + + res_length = NTP_HEADER_LENGTH; + + if (NTP_LVM_TO_VERSION(res->lvm) == 4 && random() % 2) { + unsigned char buf[128]; + + memset(buf, 0, sizeof (buf)); + efs = random() % 5; + + for (i = 0; i < efs; i++) { + ef_len = (i + 1 == efs ? NTP_MAX_V4_MAC_LENGTH + 4 : NTP_MIN_EF_LENGTH) + + 4 * (random() % 10); + TEST_CHECK(NEF_SetField((unsigned char *)res, sizeof (*res), res_length, 0, + buf, ef_len - 4, &ef_len)); + res_length += ef_len; + } + } + + if (authenticated) { + key_id = ntohl(*(uint32_t *)req->extensions); + if (key_id == 0) + key_id = get_random_key_id(); + auth_len = KEY_GetAuthLength(key_id); + assert(auth_len); + if (NTP_LVM_TO_VERSION(res->lvm) == 4) + auth_len = MIN(auth_len, NTP_MAX_V4_MAC_LENGTH - 4); + + if (KEY_GenerateAuth(key_id, res, res_length, + (unsigned char *)res + res_length + 4, auth_len) != auth_len) + assert(0); + res_length += 4 + auth_len; + } + + if (!valid_auth && authenticated) { + assert(auth_len); + + switch (random() % 5) { + case 0: + key_id++; + break; + case 1: + key_id ^= 1; + if (KEY_GenerateAuth(key_id, res, res_length - auth_len - 4, + (unsigned char *)res + res_length - auth_len, auth_len) != auth_len) + assert(0); + break; + case 2: + ((unsigned char *)res)[res_length - auth_len + random() % auth_len]++; + break; + case 3: + res_length -= 4 + auth_len; + auth_len = 4 * (random() % (auth_len / 4)); + res_length += 4 + auth_len; + break; + case 4: + if (NTP_LVM_TO_VERSION(res->lvm) == 4 && random() % 2 && + KEY_GetAuthLength(key_id) > NTP_MAX_V4_MAC_LENGTH - 4) { + res_length -= 4 + auth_len; + auth_len += 4 + 4 * (random() % + ((KEY_GetAuthLength(key_id) - NTP_MAX_V4_MAC_LENGTH - 4) / 4)); + if (KEY_GenerateAuth(key_id, res, res_length, + (unsigned char *)res + res_length + 4, auth_len) != auth_len) + assert(0); + res_length += 4 + auth_len; + } else { + memset((unsigned char *)res + res_length, 0, 4); + auth_len += 4; + res_length += 4; + } + break; + default: + assert(0); + } + } + + assert(res_length <= sizeof (*res)); + assert(res_length >= NTP_HEADER_LENGTH + auth_len); + + if (authenticated) + *(uint32_t *)((unsigned char *)res + res_length - auth_len - 4) = htonl(key_id); +} + +static void +proc_response(NCR_Instance inst, int good, int valid, int updated_sync, int updated_init) +{ + NTP_Local_Address local_addr; + NTP_Local_Timestamp local_ts; + NTP_Packet *res; + uint32_t prev_rx_count, prev_valid_count; + struct timespec prev_rx_ts, prev_init_rx_ts; + int ret; + + res = &res_buffer; + + local_addr.ip_addr.family = IPADDR_UNSPEC; + local_addr.if_index = INVALID_IF_INDEX; + local_addr.sock_fd = NTP_LVM_TO_MODE(res->lvm) != MODE_SERVER ? 100 : 101; + local_ts.ts = current_time; + local_ts.err = 0.0; + local_ts.source = NTP_TS_KERNEL; + + prev_rx_count = inst->report.total_rx_count; + prev_valid_count = inst->report.total_valid_count; + prev_rx_ts = inst->local_rx.ts; + prev_init_rx_ts = inst->init_local_rx.ts; + + ret = NCR_ProcessRxKnown(inst, &local_addr, &local_ts, res, res_length); + + if (good > 0) + TEST_CHECK(ret); + else if (!good) + TEST_CHECK(!ret); + + TEST_CHECK(prev_rx_count + 1 == inst->report.total_rx_count); + + if (valid) + TEST_CHECK(prev_valid_count + 1 == inst->report.total_valid_count); + else + TEST_CHECK(prev_valid_count == inst->report.total_valid_count); + + if (updated_sync) + TEST_CHECK(UTI_CompareTimespecs(&inst->local_rx.ts, &prev_rx_ts)); + else + TEST_CHECK(!UTI_CompareTimespecs(&inst->local_rx.ts, &prev_rx_ts)); + + if (updated_init > 0) + TEST_CHECK(UTI_CompareTimespecs(&inst->init_local_rx.ts, &prev_init_rx_ts)); + else if (!updated_init) + TEST_CHECK(!UTI_CompareTimespecs(&inst->init_local_rx.ts, &prev_init_rx_ts)); + + if (valid) { + TEST_CHECK(UTI_IsZeroTimespec(&inst->init_local_rx.ts)); + TEST_CHECK(UTI_IsZeroNtp64(&inst->init_remote_ntp_tx)); + } +} + +static void +process_replay(NCR_Instance inst, NTP_Packet *packet_queue, + int queue_length, int updated_init) +{ + do { + res_buffer = packet_queue[random() % queue_length]; + } while (!UTI_CompareNtp64(&res_buffer.transmit_ts, &inst->remote_ntp_tx)); + proc_response(inst, 0, 0, 0, updated_init); + advance_time(1e-6); +} + +static void +add_dummy_auth(NTP_AuthMode auth_mode, uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info) +{ + unsigned char buf[64]; + int len, fill; + + info->auth.mode = auth_mode; + + switch (auth_mode) { + case NTP_AUTH_NONE: + break; + case NTP_AUTH_SYMMETRIC: + case NTP_AUTH_MSSNTP: + case NTP_AUTH_MSSNTP_EXT: + switch (auth_mode) { + case NTP_AUTH_SYMMETRIC: + len = 16 + random() % 2 * 4; + fill = 1 + random() % 255; + break; + case NTP_AUTH_MSSNTP: + len = 16; + fill = 0; + break; + case NTP_AUTH_MSSNTP_EXT: + len = 68; + fill = 0; + break; + default: + assert(0); + } + + assert(info->length + 4 + len <= sizeof (*packet)); + + *(uint32_t *)((unsigned char *)packet + info->length) = htonl(key_id); + info->auth.mac.key_id = key_id; + info->length += 4; + + memset((unsigned char *)packet + info->length, fill, len); + info->length += len; + break; + case NTP_AUTH_NTS: + memset(buf, 0, sizeof (buf)); + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_AUTH_AND_EEF, buf, sizeof (buf))); + break; + default: + assert(0); + } +} + +#define PACKET_QUEUE_LENGTH 10 + +void +test_unit(void) +{ + char source_line[] = "127.0.0.1 maxdelaydevratio 1e6 noselect"; + char conf[][100] = { + "allow", + "port 0", + "local", + "keyfile ntp_core.keys" + }; + int i, j, k, interleaved, authenticated, valid, updated, has_updated; + CPS_NTP_Source source; + NTP_Remote_Address remote_addr; + NCR_Instance inst1, inst2; + NTP_Packet packet_queue[PACKET_QUEUE_LENGTH], packet; + NTP_PacketInfo info; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + LCL_Initialise(); + TST_RegisterDummyDrivers(); + SCH_Initialise(); + SRC_Initialise(); + NIO_Initialise(); + NCR_Initialise(); + REF_Initialise(); + KEY_Initialise(); + CLG_Initialise(); + + CNF_SetupAccessRestrictions(); + + CPS_ParseNTPSourceAdd(source_line, &source); + + for (i = 0; i < 1000; i++) { + source.params.interleaved = random() % 2; + source.params.authkey = random() % 2 ? get_random_key_id() : INACTIVE_AUTHKEY; + source.params.version = random() % 4 + 1; + + UTI_ZeroTimespec(¤t_time); +#if HAVE_LONG_TIME_T + advance_time(NTP_ERA_SPLIT); +#endif + advance_time(TST_GetRandomDouble(1.0, 1e9)); + + TST_GetRandomAddress(&remote_addr.ip_addr, IPADDR_UNSPEC, -1); + remote_addr.port = 123; + + inst1 = NCR_CreateInstance(&remote_addr, random() % 2 ? NTP_SERVER : NTP_PEER, + &source.params, NULL); + NCR_StartInstance(inst1); + has_updated = 0; + + for (j = 0; j < 50; j++) { + DEBUG_LOG("client/peer test iteration %d/%d", i, j); + + interleaved = random() % 2 && (inst1->mode != MODE_CLIENT || + inst1->tx_count < MAX_CLIENT_INTERLEAVED_TX); + authenticated = random() % 2; + valid = (!interleaved || (source.params.interleaved && has_updated)) && + ((source.params.authkey == INACTIVE_AUTHKEY) == !authenticated); + updated = (valid || inst1->mode == MODE_ACTIVE) && + ((source.params.authkey == INACTIVE_AUTHKEY) == !authenticated); + has_updated = has_updated || updated; + if (inst1->mode == MODE_CLIENT) + updated = 0; + + DEBUG_LOG("authkey=%d version=%d interleaved=%d authenticated=%d valid=%d updated=%d has_updated=%d", + (int)source.params.authkey, source.params.version, + interleaved, authenticated, valid, updated, has_updated); + + send_request(inst1); + + send_response(interleaved, authenticated, 1, 0, 1); + DEBUG_LOG("response 1"); + proc_response(inst1, 0, 0, 0, updated); + + if (source.params.authkey) { + send_response(interleaved, authenticated, 1, 1, 0); + DEBUG_LOG("response 2"); + proc_response(inst1, 0, 0, 0, 0); + } + + send_response(interleaved, authenticated, 1, 1, 1); + DEBUG_LOG("response 3"); + proc_response(inst1, -1, valid, valid, updated); + DEBUG_LOG("response 4"); + proc_response(inst1, 0, 0, 0, 0); + + advance_time(-1.0); + + send_response(interleaved, authenticated, 1, 1, 1); + DEBUG_LOG("response 5"); + proc_response(inst1, 0, 0, 0, updated && valid); + + advance_time(1.0); + + send_response(interleaved, authenticated, 1, 1, 1); + DEBUG_LOG("response 6"); + proc_response(inst1, 0, 0, valid && updated, updated); + } + + NCR_DestroyInstance(inst1); + + inst1 = NCR_CreateInstance(&remote_addr, random() % 2 ? NTP_SERVER : NTP_PEER, + &source.params, NULL); + NCR_StartInstance(inst1); + + for (j = 0; j < 20; j++) { + DEBUG_LOG("server test iteration %d/%d", i, j); + + send_request(inst1); + process_request(&remote_addr); + proc_response(inst1, + !source.params.interleaved || source.params.version != 4 || + inst1->mode == MODE_ACTIVE || j != 2, + 1, 1, 0); + advance_time(1 << inst1->local_poll); + } + + NCR_DestroyInstance(inst1); + + inst1 = NCR_CreateInstance(&remote_addr, NTP_PEER, &source.params, NULL); + NCR_StartInstance(inst1); + inst2 = NCR_CreateInstance(&remote_addr, NTP_PEER, &source.params, NULL); + NCR_StartInstance(inst2); + + res_length = req_length = 0; + + for (j = 0; j < 20; j++) { + DEBUG_LOG("peer replay test iteration %d/%d", i, j); + + send_request(inst1); + res_buffer = req_buffer; + assert(!res_length || res_length == req_length); + res_length = req_length; + + TEST_CHECK(inst1->valid_timestamps == (j > 0)); + + DEBUG_LOG("response 1->2"); + proc_response(inst2, j > source.params.interleaved, j > 0, j > 0, 1); + + packet_queue[(j * 2) % PACKET_QUEUE_LENGTH] = res_buffer; + + for (k = 0; k < j % 4 + 1; k++) { + DEBUG_LOG("replay ?->1 %d", k); + process_replay(inst1, packet_queue, MIN(j * 2 + 1, PACKET_QUEUE_LENGTH), k ? -1 : 1); + DEBUG_LOG("replay ?->2 %d", k); + process_replay(inst2, packet_queue, MIN(j * 2 + 1, PACKET_QUEUE_LENGTH), -1); + } + + advance_time(1 << (source.params.minpoll - 1)); + + send_request(inst2); + res_buffer = req_buffer; + assert(res_length == req_length); + + TEST_CHECK(inst2->valid_timestamps == (j > 0)); + + DEBUG_LOG("response 2->1"); + proc_response(inst1, 1, 1, 1, 1); + + packet_queue[(j * 2 + 1) % PACKET_QUEUE_LENGTH] = res_buffer; + + for (k = 0; k < j % 4 + 1; k++) { + DEBUG_LOG("replay ?->1 %d", k); + process_replay(inst1, packet_queue, MIN(j * 2 + 2, PACKET_QUEUE_LENGTH), k ? -1 : 1); + DEBUG_LOG("replay ?->2 %d", k); + process_replay(inst2, packet_queue, MIN(j * 2 + 2, PACKET_QUEUE_LENGTH), -1); + } + + advance_time(1 << (source.params.minpoll - 1)); + } + + NCR_DestroyInstance(inst1); + NCR_DestroyInstance(inst2); + } + + memset(&packet, 0, sizeof (packet)); + packet.lvm = NTP_LVM(LEAP_Normal, NTP_VERSION, MODE_CLIENT); + + TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info)); + TEST_CHECK(info.auth.mode == NTP_AUTH_NONE); + + TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info)); + add_dummy_auth(NTP_AUTH_SYMMETRIC, 100, &packet, &info); + memset(&info.auth, 0, sizeof (info.auth)); + TEST_CHECK(parse_packet(&packet, info.length, &info)); + TEST_CHECK(info.auth.mode == NTP_AUTH_SYMMETRIC); + TEST_CHECK(info.auth.mac.start == NTP_HEADER_LENGTH); + TEST_CHECK(info.auth.mac.length == info.length - NTP_HEADER_LENGTH); + TEST_CHECK(info.auth.mac.key_id == 100); + + TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info)); + add_dummy_auth(NTP_AUTH_NTS, 0, &packet, &info); + memset(&info.auth, 0, sizeof (info.auth)); + TEST_CHECK(parse_packet(&packet, info.length, &info)); + TEST_CHECK(info.auth.mode == NTP_AUTH_NTS); + + packet.lvm = NTP_LVM(LEAP_Normal, 3, MODE_CLIENT); + + TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info)); + add_dummy_auth(NTP_AUTH_MSSNTP, 200, &packet, &info); + memset(&info.auth, 0, sizeof (info.auth)); + TEST_CHECK(parse_packet(&packet, info.length, &info)); + TEST_CHECK(info.auth.mode == NTP_AUTH_MSSNTP); + TEST_CHECK(info.auth.mac.start == NTP_HEADER_LENGTH); + TEST_CHECK(info.auth.mac.length == 20); + TEST_CHECK(info.auth.mac.key_id == 200); + + TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info)); + add_dummy_auth(NTP_AUTH_MSSNTP_EXT, 300, &packet, &info); + memset(&info.auth, 0, sizeof (info.auth)); + TEST_CHECK(parse_packet(&packet, info.length, &info)); + TEST_CHECK(info.auth.mode == NTP_AUTH_MSSNTP_EXT); + TEST_CHECK(info.auth.mac.start == NTP_HEADER_LENGTH); + TEST_CHECK(info.auth.mac.length == 72); + TEST_CHECK(info.auth.mac.key_id == 300); + + CLG_Finalise(); + KEY_Finalise(); + REF_Finalise(); + NCR_Finalise(); + NIO_Finalise(); + SRC_Finalise(); + SCH_Finalise(); + LCL_Finalise(); + CNF_Finalise(); + HSH_Finalise(); +} + +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/ntp_core.keys b/test/unit/ntp_core.keys new file mode 100644 index 0000000..a3e4f6c --- /dev/null +++ b/test/unit/ntp_core.keys @@ -0,0 +1,8 @@ +2 MD5 HEX:38979C567358C0896F4D9D459A3C8B8478654579 +3 MD5 HEX:38979C567358C0896F4D9D459A3C8B8478654579 +4 SHA1 HEX:B71744EA01FBF01CA30D173ECDDF901952AE356A +5 SHA1 HEX:B71744EA01FBF01CA30D173ECDDF901952AE356A +6 SHA512 HEX:DE027482F22B201FC20863F58C74095E7906089F +7 SHA512 HEX:DE027482F22B201FC20863F58C74095E7906089F +8 AES128 HEX:5D5E8A31D4B459A66D445259E147CFB5 +9 AES128 HEX:5D5E8A31D4B459A66D445259E147CFB5 diff --git a/test/unit/ntp_ext.c b/test/unit/ntp_ext.c new file mode 100644 index 0000000..c37e702 --- /dev/null +++ b/test/unit/ntp_ext.c @@ -0,0 +1,167 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#ifdef FEAT_NTP + +#include <util.h> +#include <logging.h> + +#include <ntp_ext.c> + +void +test_unit(void) +{ + unsigned char *buffer, body[NTP_MAX_EXTENSIONS_LENGTH]; + void *bodyp; + NTP_PacketInfo info; + NTP_Packet packet; + int i, j, start, length, type, type2, body_length, body_length2; + + assert(sizeof (uint16_t) == 2); + assert(sizeof (body) == sizeof (packet.extensions)); + + buffer = (unsigned char *)packet.extensions; + + for (i = 0; i < 10000; i++) { + body_length = random() % (sizeof (body) - 4 + 1) / 4 * 4; + start = random() % (sizeof (packet.extensions) - body_length - 4 + 1) / 4 * 4; + type = random() % 0x10000; + + DEBUG_LOG("body_length=%d start=%d type=%d", body_length, start, type); + assert(body_length + start <= sizeof (packet.extensions)); + + UTI_GetRandomBytes(body, body_length); + + TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start, + type, body, body_length + 4, &length)); + TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start + 4, + type, body, body_length, &length)); + TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start, + type, body, body_length - 1, &length)); + TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start, + type, body, body_length - 2, &length)); + TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start, + type, body, body_length - 3, &length)); + TEST_CHECK(!NEF_SetField(buffer, body_length + start + 3, start, + type, body, body_length, &length)); + TEST_CHECK(!NEF_SetField(buffer, body_length + start + 5, start + 1, + type, body, body_length, &length)); + + TEST_CHECK(NEF_SetField(buffer, body_length + start + 4, start, + type, body, body_length, &length)); + TEST_CHECK(length == body_length + 4); + TEST_CHECK(((uint16_t *)buffer)[start / 2] == htons(type)); + TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] == htons(length)); + TEST_CHECK(memcmp(buffer + start + 4, body, body_length) == 0); + + memset(&packet, 0, sizeof (packet)); + packet.lvm = NTP_LVM(0, 4, MODE_CLIENT); + memset(&info, 0, sizeof (info)); + + info.version = 3; + info.length = NTP_HEADER_LENGTH; + TEST_CHECK(!NEF_AddBlankField(&packet, &info, type, body_length, &bodyp)); + + info.version = 4; + info.length = NTP_HEADER_LENGTH - 4; + TEST_CHECK(!NEF_AddBlankField(&packet, &info, type, body_length, &bodyp)); + + info.length = sizeof (packet) - body_length; + TEST_CHECK(!NEF_AddBlankField(&packet, &info, type, body_length, &bodyp)); + + info.length = NTP_HEADER_LENGTH + start; + + if (body_length < 12) { + TEST_CHECK(!NEF_AddBlankField(&packet, &info, type, body_length, &bodyp)); + continue; + } + + TEST_CHECK(NEF_AddBlankField(&packet, &info, type, body_length, &bodyp)); + TEST_CHECK(info.length == NTP_HEADER_LENGTH + start + body_length + 4); + TEST_CHECK(((uint16_t *)buffer)[start / 2] == htons(type)); + TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] == htons(length)); + TEST_CHECK(bodyp == buffer + start + 4); + TEST_CHECK(info.ext_fields == 1); + + memset(buffer, 0, sizeof (packet.extensions)); + info.length = NTP_HEADER_LENGTH + start; + info.ext_fields = 0; + + TEST_CHECK(NEF_AddField(&packet, &info, type, body, body_length)); + TEST_CHECK(info.length == NTP_HEADER_LENGTH + start + body_length + 4); + TEST_CHECK(((uint16_t *)buffer)[start / 2] == htons(type)); + TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] == htons(length)); + TEST_CHECK(memcmp(buffer + start + 4, body, body_length) == 0); + TEST_CHECK(info.ext_fields == 1); + + for (j = 1; j <= 4; j++) { + TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] = htons(length + j)); + TEST_CHECK(!NEF_ParseSingleField(buffer, start + body_length + 4, start, + &length, &type2, &bodyp, &body_length2)); + } + + TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] = htons(length)); + + TEST_CHECK(NEF_ParseSingleField(buffer, sizeof (packet.extensions), start, + &length, &type2, &bodyp, &body_length2)); + TEST_CHECK(length == body_length + 4); + TEST_CHECK(type2 == type); + TEST_CHECK(bodyp == buffer + start + 4); + TEST_CHECK(body_length2 == body_length); + + TEST_CHECK(!NEF_ParseField(&packet, sizeof (packet) + 4, + NTP_HEADER_LENGTH + start, + &length, &type2, &bodyp, &body_length2)); + + if (body_length < 24) { + TEST_CHECK(!NEF_ParseField(&packet, NTP_HEADER_LENGTH + start + length, + NTP_HEADER_LENGTH + start, + &length, &type2, &bodyp, &body_length2)); + if (sizeof (packet.extensions) - start <= 24) { + TEST_CHECK(!NEF_ParseField(&packet, sizeof (packet), NTP_HEADER_LENGTH + start, + &length, &type2, &bodyp, &body_length2)); + continue; + } else { + TEST_CHECK(NEF_ParseField(&packet, sizeof (packet), NTP_HEADER_LENGTH + start, + &length, &type2, &bodyp, &body_length2)); + } + } else { + TEST_CHECK(NEF_ParseField(&packet, NTP_HEADER_LENGTH + start + length, + NTP_HEADER_LENGTH + start, + &length, &type2, &bodyp, &body_length2)); + } + TEST_CHECK(length == body_length + 4); + TEST_CHECK(type2 == type); + TEST_CHECK(bodyp == buffer + start + 4); + TEST_CHECK(body_length2 == body_length); + + } +} + +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/ntp_sources.c b/test/unit/ntp_sources.c new file mode 100644 index 0000000..ea3910f --- /dev/null +++ b/test/unit/ntp_sources.c @@ -0,0 +1,364 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2016, 2021 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#ifdef FEAT_NTP + +#include <conf.h> +#include <cmdparse.h> +#include <nameserv_async.h> +#include <ntp_core.h> +#include <ntp_io.h> + +static char *requested_name = NULL; +static DNS_NameResolveHandler resolve_handler = NULL; +static void *resolve_handler_arg = NULL; + +#define DNS_Name2IPAddressAsync(name, handler, arg) \ + requested_name = (name), \ + resolve_handler = (handler), \ + resolve_handler_arg = (arg) +#define NCR_ChangeRemoteAddress(inst, remote_addr, ntp_only) \ + change_remote_address(inst, remote_addr, ntp_only) +#define NCR_ProcessRxKnown(remote_addr, local_addr, ts, msg, len) (random() % 2) +#define NIO_IsServerConnectable(addr) (random() % 2) + +static void change_remote_address(NCR_Instance inst, NTP_Remote_Address *remote_addr, + int ntp_only); + +#include <ntp_sources.c> + +#undef NCR_ChangeRemoteAddress + +static void +resolve_random_address(DNS_Status status, int rand_bits) +{ + IPAddr ip_addrs[DNS_MAX_ADDRESSES]; + int i, n_addrs; + + TEST_CHECK(requested_name); + requested_name = NULL; + + if (status == DNS_Success) { + n_addrs = random() % DNS_MAX_ADDRESSES + 1; + for (i = 0; i < n_addrs; i++) + TST_GetRandomAddress(&ip_addrs[i], IPADDR_UNSPEC, rand_bits); + } else { + n_addrs = 0; + } + + (resolve_handler)(status, n_addrs, ip_addrs, resolve_handler_arg); +} + +static int +update_random_address(NTP_Remote_Address *addr, int rand_bits) +{ + NTP_Remote_Address new_addr; + NSR_Status status; + + TST_GetRandomAddress(&new_addr.ip_addr, IPADDR_UNSPEC, rand_bits); + new_addr.port = random() % 1024; + + status = NSR_UpdateSourceNtpAddress(addr, &new_addr); + if (status == NSR_InvalidAF) { + TEST_CHECK(!UTI_IsIPReal(&addr->ip_addr)); + } else { + TEST_CHECK(status == NSR_Success || status == NSR_AlreadyInUse); + } + + return status == NSR_Success; +} + +static void +change_remote_address(NCR_Instance inst, NTP_Remote_Address *remote_addr, int ntp_only) +{ + int update = !ntp_only && random() % 4 == 0, update_pos = random() % 2, r = 0; + + TEST_CHECK(record_lock); + + if (update && update_pos == 0) + r = update_random_address(remote_addr, 4); + + NCR_ChangeRemoteAddress(inst, remote_addr, ntp_only); + + if (update && update_pos == 1) + r = update_random_address(remote_addr, 4); + + if (r) + TEST_CHECK(UTI_IsIPReal(&saved_address_update.old_address.ip_addr)); +} + +void +test_unit(void) +{ + char source_line[] = "127.0.0.1 offline", conf[] = "port 0", name[64]; + int i, j, k, slot, found, pool, prev_n; + uint32_t hash = 0, conf_id; + NTP_Remote_Address addrs[256], addr; + NTP_Local_Address local_addr; + NTP_Local_Timestamp local_ts; + struct UnresolvedSource *us; + RPT_ActivityReport report; + CPS_NTP_Source source; + NSR_Status status; + NTP_Packet msg; + + CNF_Initialise(0, 0); + CNF_ParseLine(NULL, 1, conf); + + PRV_Initialise(); + LCL_Initialise(); + TST_RegisterDummyDrivers(); + SCH_Initialise(); + SRC_Initialise(); + NIO_Initialise(); + NCR_Initialise(); + REF_Initialise(); + NSR_Initialise(); + + CPS_ParseNTPSourceAdd(source_line, &source); + + TEST_CHECK(n_sources == 0); + + for (i = 0; i < 6; i++) { + TEST_CHECK(ARR_GetSize(records) == 1); + + DEBUG_LOG("collision mod %u", 1U << i); + + for (j = 0; j < sizeof (addrs) / sizeof (addrs[0]); j++) { + while (1) { + do { + TST_GetRandomAddress(&addrs[j].ip_addr, IPADDR_UNSPEC, -1); + } while (UTI_IPToHash(&addrs[j].ip_addr) % (1U << i) != hash % (1U << i)); + + for (k = 0; k < j; k++) + if (UTI_CompareIPs(&addrs[k].ip_addr, &addrs[j].ip_addr, NULL) == 0) + break; + if (k == j) + break; + } + + addrs[j].port = random() % 1024; + + if (!j) + hash = UTI_IPToHash(&addrs[j].ip_addr); + + DEBUG_LOG("adding source %s hash %"PRIu32, UTI_IPToString(&addrs[j].ip_addr), + UTI_IPToHash(&addrs[j].ip_addr) % (1U << i)); + + status = NSR_AddSource(&addrs[j], random() % 2 ? NTP_SERVER : NTP_PEER, + &source.params, NULL); + TEST_CHECK(status == NSR_Success); + TEST_CHECK(n_sources == j + 1); + + TEST_CHECK(strcmp(NSR_GetName(&addrs[j].ip_addr), UTI_IPToString(&addrs[j].ip_addr)) == 0); + + for (k = 0; k <= j; k++) { + addr = addrs[k]; + found = find_slot2(&addr, &slot); + TEST_CHECK(found == 2); + TEST_CHECK(!UTI_CompareIPs(&get_record(slot)->remote_addr->ip_addr, + &addr.ip_addr, NULL)); + addr.port++; + found = find_slot2(&addr, &slot); + TEST_CHECK(found == 1); + TEST_CHECK(!UTI_CompareIPs(&get_record(slot)->remote_addr->ip_addr, + &addr.ip_addr, NULL)); + } + + status = NSR_AddSource(&addrs[j], NTP_SERVER, &source.params, &conf_id); + TEST_CHECK(status == NSR_AlreadyInUse); + } + + for (j = 0; j < sizeof (addrs) / sizeof (addrs[0]); j++) { + DEBUG_LOG("removing source %s", UTI_IPToString(&addrs[j].ip_addr)); + NSR_RemoveSource(&addrs[j].ip_addr); + + for (k = 0; k < sizeof (addrs) / sizeof (addrs[0]); k++) { + found = find_slot2(&addrs[k], &slot); + TEST_CHECK(found == (k <= j ? 0 : 2)); + } + } + } + + TEST_CHECK(n_sources == 0); + + status = NSR_AddSourceByName("a b", 0, 0, 0, &source.params, &conf_id); + TEST_CHECK(status == NSR_InvalidName); + + local_addr.ip_addr.family = IPADDR_INET4; + local_addr.ip_addr.addr.in4 = 0; + local_addr.if_index = -1; + local_addr.sock_fd = 0; + memset(&local_ts, 0, sizeof (local_ts)); + + for (i = 0; i < 500; i++) { + for (j = 0; j < 20; j++) { + snprintf(name, sizeof (name), "ntp%d.example.net", (int)(random() % 10)); + pool = random() % 2; + prev_n = n_sources; + + DEBUG_LOG("%d/%d adding source %s pool=%d", i, j, name, pool); + status = NSR_AddSourceByName(name, 0, pool, random() % 2 ? NTP_SERVER : NTP_PEER, + &source.params, &conf_id); + TEST_CHECK(status == NSR_UnresolvedName); + + TEST_CHECK(n_sources == prev_n + (pool ? source.params.max_sources * 2 : 1)); + TEST_CHECK(unresolved_sources); + + for (us = unresolved_sources; us->next; us = us->next) + ; + TEST_CHECK(strcmp(us->name, name) == 0); + if (pool) { + TEST_CHECK(us->address.ip_addr.family == IPADDR_UNSPEC && us->pool_id >= 0); + } else { + TEST_CHECK(strcmp(NSR_GetName(&us->address.ip_addr), name) == 0); + TEST_CHECK(find_slot2(&us->address, &slot) == 2); + } + + if (random() % 2) { + if (!resolving_id || random() % 2) { + NSR_ResolveSources(); + } else { + SCH_RemoveTimeout(resolving_id); + resolve_sources_timeout(NULL); + TEST_CHECK(resolving_id == 0); + TEST_CHECK(requested_name); + } + + TEST_CHECK(!!unresolved_sources == (resolving_id != 0) || requested_name); + } + + while (requested_name && random() % 2) { + TEST_CHECK(resolving_source); + TEST_CHECK(strcmp(requested_name, resolving_source->name) == 0); + TEST_CHECK(!record_lock); + + switch (random() % 3) { + case 0: + resolve_random_address(DNS_Success, 4); + break; + case 1: + resolve_random_address(DNS_TryAgain, 0); + break; + case 2: + resolve_random_address(DNS_Failure, 0); + break; + } + } + + while (random() % 8 > 0) { + slot = random() % ARR_GetSize(records); + if (!get_record(slot)->remote_addr) + continue; + + switch (random() % 5) { + case 0: + msg.lvm = NTP_LVM(0, NTP_VERSION, random() % 2 ? MODE_CLIENT : MODE_SERVER); + NSR_ProcessTx(get_record(slot)->remote_addr, &local_addr, + &local_ts, &msg, 0); + break; + case 1: + msg.lvm = NTP_LVM(0, NTP_VERSION, random() % 2 ? MODE_CLIENT : MODE_SERVER); + NSR_ProcessRx(get_record(slot)->remote_addr, &local_addr, + &local_ts, &msg, 0); + break; + case 2: + NSR_HandleBadSource(&get_record(slot)->remote_addr->ip_addr); + break; + case 3: + NSR_SetConnectivity(NULL, &get_record(slot)->remote_addr->ip_addr, SRC_OFFLINE); + break; + case 4: + update_random_address(get_record(slot)->remote_addr, 4); + TEST_CHECK(!UTI_IsIPReal(&saved_address_update.old_address.ip_addr)); + break; + } + + TEST_CHECK(!record_lock); + } + + NSR_GetActivityReport(&report); + TEST_CHECK(report.online == 0); + TEST_CHECK(report.offline >= 0); + TEST_CHECK(report.burst_online == 0); + TEST_CHECK(report.burst_offline == 0); + TEST_CHECK(report.unresolved >= 0); + + if (random() % 4 == 0) { + NSR_RemoveSourcesById(conf_id); + TEST_CHECK(n_sources <= prev_n); + } else if (random() % 8 == 0) { + NSR_RefreshAddresses(); + TEST_CHECK(unresolved_sources); + } + } + + NSR_RemoveAllSources(); + TEST_CHECK(n_sources == 0); + + for (j = 0; j < ARR_GetSize(pools); j++) { + TEST_CHECK(get_pool(j)->sources == 0); + TEST_CHECK(get_pool(j)->unresolved_sources == 0); + TEST_CHECK(get_pool(j)->confirmed_sources == 0); + TEST_CHECK(get_pool(j)->max_sources == 0); + } + + while (requested_name) { + TEST_CHECK(resolving_source); + resolve_random_address(random() % 2 ? DNS_Success : DNS_TryAgain, 4); + } + + if (unresolved_sources && resolving_id == 0) + NSR_ResolveSources(); + + TEST_CHECK(!!unresolved_sources == (resolving_id != 0)); + + if (resolving_id) { + SCH_RemoveTimeout(resolving_id); + resolve_sources_timeout(NULL); + } + + TEST_CHECK(resolving_id == 0); + TEST_CHECK(!requested_name); + TEST_CHECK(!unresolved_sources); + } + + NSR_Finalise(); + REF_Finalise(); + NCR_Finalise(); + NIO_Finalise(); + SRC_Finalise(); + SCH_Finalise(); + LCL_Finalise(); + PRV_Finalise(); + CNF_Finalise(); + HSH_Finalise(); +} + +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ke.crt b/test/unit/nts_ke.crt new file mode 100644 index 0000000..8165287 --- /dev/null +++ b/test/unit/nts_ke.crt @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE----- +MIIBDjCBwaADAgECAgEBMAUGAytlcDAPMQ0wCwYDVQQDEwR0ZXN0MCAXDTcwMDEw +MTAwMDAwMFoYDzIxMDAwMTAyMDAwMDAwWjAPMQ0wCwYDVQQDEwR0ZXN0MCowBQYD +K2VwAyEA3oh/FZeOxRYvJVLfCDEwGI6Oe23gTgLHx8a87tvwgfyjQDA+MAwGA1Ud +EwEB/wQCMAAwDwYDVR0PAQH/BAUDAweAADAdBgNVHQ4EFgQUYwAqF9q3jxUk68m1 +cuz8DueOHeMwBQYDK2VwA0EAne0dCRXb0dW8bn2v3RhVTqeTJWXfl74x8MTQMTM7 +/uGTqoYOA0YJffypd+p27pvx2BEoNQWRYM6pqBg55KbwDw== +-----END CERTIFICATE----- diff --git a/test/unit/nts_ke.key b/test/unit/nts_ke.key new file mode 100644 index 0000000..6414c64 --- /dev/null +++ b/test/unit/nts_ke.key @@ -0,0 +1,25 @@ +Public Key Info: + Public Key Algorithm: EdDSA (Ed25519) + Key Security Level: High (256 bits) + +curve: Ed25519 +private key: + 01:d9:75:42:7c:52:cc:29:9e:90:01:f3:da:26:f6:d7 + ad:af:a5:2a:82:36:1d:86:c6:57:a7:b4:99:9b:6c:6d + + +x: + de:88:7f:15:97:8e:c5:16:2f:25:52:df:08:31:30:18 + 8e:8e:7b:6d:e0:4e:02:c7:c7:c6:bc:ee:db:f0:81:fc + + + +Public Key PIN: + pin-sha256:C4LBJP2cRxvLcZ6pjowcOEQhcW3ZPMVTpLgRGsBDeMw= +Public Key ID: + sha256:0b82c124fd9c471bcb719ea98e8c1c384421716dd93cc553a4b8111ac04378cc + sha1:63002a17dab78f1524ebc9b572ecfc0ee78e1de3 + +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIAHZdUJ8UswpnpAB89om9tetr6UqgjYdhsZXp7SZm2xt +-----END PRIVATE KEY----- diff --git a/test/unit/nts_ke_client.c b/test/unit/nts_ke_client.c new file mode 100644 index 0000000..72690bf --- /dev/null +++ b/test/unit/nts_ke_client.c @@ -0,0 +1,144 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#ifdef FEAT_NTS + +#include <nts_ke_client.c> +#include <local.h> + +static void +prepare_response(NKSN_Instance session, int valid) +{ + uint16_t data[16]; + int i, index, length; + + if (valid) + index = -1; + else + index = random() % 10; + DEBUG_LOG("index=%d", index); + + NKSN_BeginMessage(session); + + memset(data, 0, sizeof (data)); + length = 2; + assert(sizeof (data[0]) == 2); + + if (index == 0) { + data[0] = htons(random() % 100); + TEST_CHECK(NKSN_AddRecord(session, 1, random() % 2 ? NKE_RECORD_ERROR : NKE_RECORD_WARNING, + data, length)); + } else if (index == 1) { + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_ERROR + 1000, data, length)); + } + + if (index != 2) { + if (index == 3) + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4 + random() % 10 + 1); + else + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); + if (index == 4) + length = 3 + random() % 10; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); + } + + if (index != 5) { + if (index == 6) + data[0] = htons(AEAD_AES_SIV_CMAC_256 + random() % 10 + 1); + else + data[0] = htons(AEAD_AES_SIV_CMAC_256); + if (index == 7) + length = 3 + random() % 10; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); + } + + if (random() % 2) { + const char server[] = "127.0.0.1"; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_SERVER_NEGOTIATION, + server, sizeof (server) - 1)); + } + + if (random() % 2) { + data[0] = htons(123); + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_PORT_NEGOTIATION, data, length)); + } + + if (random() % 2) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, 1000 + random() % 1000, data, length)); + } + + if (index != 8) { + for (i = 0; i < NKE_MAX_COOKIES; i++) { + length = (random() % sizeof (data) + 1) / 4 * 4; + if (index == 9) + length += (length < sizeof (data) ? 1 : -1) * (random() % 3 + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_COOKIE, data, length)); + } + } + + TEST_CHECK(NKSN_EndMessage(session)); +} + +void +test_unit(void) +{ + NKC_Instance inst; + IPSockAddr addr; + int i, r, valid; + + char conf[][100] = { + "nosystemcert", + }; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + LCL_Initialise(); + + SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr); + addr.port = 0; + + inst = NKC_CreateInstance(&addr, "test", 0); + TEST_CHECK(inst); + + for (i = 0; i < 10000; i++) { + valid = random() % 2; + prepare_response(inst->session, valid); + r = process_response(inst); + TEST_CHECK(r == valid); + } + + NKC_DestroyInstance(inst); + + LCL_Finalise(); + CNF_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ke_server.c b/test/unit/nts_ke_server.c new file mode 100644 index 0000000..f4f03a1 --- /dev/null +++ b/test/unit/nts_ke_server.c @@ -0,0 +1,230 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#ifdef FEAT_NTS + +#include <local.h> +#include <nts_ke_session.h> +#include <util.h> + +#define NKSN_GetKeys get_keys + +static int +get_keys(NKSN_Instance session, SIV_Algorithm siv, NKE_Key *c2s, NKE_Key *s2c) +{ + c2s->length = SIV_GetKeyLength(siv); + UTI_GetRandomBytes(c2s->key, c2s->length); + s2c->length = SIV_GetKeyLength(siv); + UTI_GetRandomBytes(s2c->key, s2c->length); + return 1; +} + +#include <nts_ke_server.c> + +static void +prepare_request(NKSN_Instance session, int valid) +{ + uint16_t data[16]; + int index, length; + + if (valid) + index = -1; + else + index = random() % 9; + DEBUG_LOG("index=%d", index); + + NKSN_BeginMessage(session); + + memset(data, 0, sizeof (data)); + length = 2; + assert(sizeof (data[0]) == 2); + + if (index != 0) { + memset(data, NKE_NEXT_PROTOCOL_NTPV4 + 1, sizeof (data)); + data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4); + if (index == 1) + length = 0; + else if (index == 2) + length = 3 + random() % 15 * 2; + else + length = 2 + random() % 16 * 2; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); + } + + if (index == 3) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length)); + + if (index != 4) { + data[0] = htons(AEAD_AES_SIV_CMAC_256); + if (index == 5) + length = 0; + else if (index == 6) + length = 3 + random() % 15 * 2; + else + length = 2 + random() % 16 * 2; + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); + } + + if (index == 7) + TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length)); + + if (index == 8) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 1, 1000 + random() % 1000, data, length)); + } + + if (random() % 2) { + const char server[] = "127.0.0.1"; + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_SERVER_NEGOTIATION, + server, sizeof (server) - 1)); + } + + if (random() % 2) { + data[0] = htons(123); + TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_PORT_NEGOTIATION, data, length)); + } + + if (random() % 2) { + length = random() % (sizeof (data) + 1); + TEST_CHECK(NKSN_AddRecord(session, 0, 1000 + random() % 1000, data, length)); + } + + TEST_CHECK(NKSN_EndMessage(session)); +} + +static void +process_response(NKSN_Instance session, int valid) +{ + int records, errors, critical, type, length; + + for (records = errors = 0; ; records++) { + if (!NKSN_GetRecord(session, &critical, &type, &length, NULL, 0)) + break; + if (type == NKE_RECORD_ERROR) + errors++; + } + + if (valid) { + TEST_CHECK(records >= 2); + } else { + TEST_CHECK(records == 1); + TEST_CHECK(errors == 1); + } +} + +void +test_unit(void) +{ + NKSN_Instance session; + NKE_Context context, context2; + NKE_Cookie cookie; + int i, valid, l; + uint32_t sum, sum2; + + char conf[][100] = { + "ntsdumpdir .", + "ntsport 0", + "ntsprocesses 0", + "ntsserverkey nts_ke.key", + "ntsservercert nts_ke.crt", + }; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + LCL_Initialise(); + TST_RegisterDummyDrivers(); + SCH_Initialise(); + + unlink("ntskeys"); + NKS_PreInitialise(0, 0, 0); + NKS_Initialise(); + + session = NKSN_CreateInstance(1, NULL, handle_message, NULL); + + for (i = 0; i < 10000; i++) { + valid = random() % 2; + prepare_request(session, valid); + TEST_CHECK(process_request(session)); + process_response(session, valid); + } + + + for (i = 0; i < 10000; i++) { + context.algorithm = AEAD_AES_SIV_CMAC_256; + get_keys(session, context.algorithm, &context.c2s, &context.s2c); + memset(&cookie, 0, sizeof (cookie)); + TEST_CHECK(NKS_GenerateCookie(&context, &cookie)); + TEST_CHECK(NKS_DecodeCookie(&cookie, &context2)); + TEST_CHECK(context.algorithm == context2.algorithm); + TEST_CHECK(context.c2s.length == context2.c2s.length); + TEST_CHECK(context.s2c.length == context2.s2c.length); + TEST_CHECK(memcmp(context.c2s.key, context2.c2s.key, context.c2s.length) == 0); + TEST_CHECK(memcmp(context.s2c.key, context2.s2c.key, context.s2c.length) == 0); + + if (random() % 4) { + cookie.cookie[random() % (cookie.length)]++; + } else if (random() % 4) { + generate_key(current_server_key); + } else { + l = cookie.length; + while (l == cookie.length) + cookie.length = random() % (sizeof (cookie.cookie) + 1); + } + TEST_CHECK(!NKS_DecodeCookie(&cookie, &context2)); + } + + unlink("ntskeys"); + save_keys(); + + for (i = 0, sum = 0; i < MAX_SERVER_KEYS; i++) { + sum += server_keys[i].id + server_keys[i].key[0]; + generate_key(i); + } + + load_keys(); + TEST_CHECK(unlink("ntskeys") == 0); + + for (i = 0, sum2 = 0; i < MAX_SERVER_KEYS; i++) { + sum2 += server_keys[i].id + server_keys[i].key[0]; + } + + TEST_CHECK(sum == sum2); + + NKSN_DestroyInstance(session); + + NKS_Finalise(); + TEST_CHECK(unlink("ntskeys") == 0); + + SCH_Finalise(); + LCL_Finalise(); + CNF_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ke_session.c b/test/unit/nts_ke_session.c new file mode 100644 index 0000000..d0e72c7 --- /dev/null +++ b/test/unit/nts_ke_session.c @@ -0,0 +1,224 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#ifdef FEAT_NTS + +#include <nts_ke_session.c> + +#include <local.h> +#include <socket.h> +#include <sched.h> + +static NKSN_Instance client, server; +static unsigned char record[NKE_MAX_MESSAGE_LENGTH]; +static int record_length, critical, type_start, records; +static int request_received; +static int response_received; + +static void +send_message(NKSN_Instance inst) +{ + int i; + + record_length = random() % (NKE_MAX_MESSAGE_LENGTH - 4 + 1); + for (i = 0; i < record_length; i++) + record[i] = random() % 256; + critical = random() % 2; + type_start = random() % 30000 + 1; + assert(sizeof (struct RecordHeader) == 4); + records = random() % ((NKE_MAX_MESSAGE_LENGTH - 4) / (4 + record_length) + 1); + + DEBUG_LOG("critical=%d type_start=%d records=%d*%d", + critical, type_start, records, record_length); + + NKSN_BeginMessage(inst); + + TEST_CHECK(check_message_format(&inst->message, 0)); + TEST_CHECK(!check_message_format(&inst->message, 1)); + + TEST_CHECK(!NKSN_AddRecord(inst, 0, 1, record, NKE_MAX_MESSAGE_LENGTH - 4 + 1)); + + TEST_CHECK(check_message_format(&inst->message, 0)); + TEST_CHECK(!check_message_format(&inst->message, 1)); + + for (i = 0; i < records; i++) { + TEST_CHECK(NKSN_AddRecord(inst, critical, type_start + i, record, record_length)); + TEST_CHECK(!NKSN_AddRecord(inst, 0, 1, &record, + NKE_MAX_MESSAGE_LENGTH - inst->message.length - 4 + 1)); + + TEST_CHECK(check_message_format(&inst->message, 0)); + TEST_CHECK(!check_message_format(&inst->message, 1)); + } + + TEST_CHECK(NKSN_EndMessage(inst)); + + TEST_CHECK(check_message_format(&inst->message, 0)); + TEST_CHECK(check_message_format(&inst->message, 1)); +} + +static void +verify_message(NKSN_Instance inst) +{ + unsigned char buffer[NKE_MAX_MESSAGE_LENGTH]; + int i, c, t, length, buffer_length, msg_length, prev_parsed; + NKE_Key c2s, s2c; + + for (i = 0; i < records; i++) { + memset(buffer, 0, sizeof (buffer)); + buffer_length = random() % (record_length + 1); + assert(buffer_length <= sizeof (buffer)); + + prev_parsed = inst->message.parsed; + msg_length = inst->message.length; + + TEST_CHECK(NKSN_GetRecord(inst, &c, &t, &length, buffer, buffer_length)); + TEST_CHECK(c == critical); + TEST_CHECK(t == type_start + i); + TEST_CHECK(length == record_length); + TEST_CHECK(memcmp(record, buffer, buffer_length) == 0); + if (buffer_length < record_length) + TEST_CHECK(buffer[buffer_length] == 0); + + inst->message.length = inst->message.parsed - 1; + inst->message.parsed = prev_parsed; + TEST_CHECK(!get_record(&inst->message, NULL, NULL, NULL, buffer, buffer_length)); + TEST_CHECK(inst->message.parsed == prev_parsed); + inst->message.length = msg_length; + if (msg_length < 0x8000) { + inst->message.data[prev_parsed + 2] ^= 0x80; + TEST_CHECK(!get_record(&inst->message, NULL, NULL, NULL, buffer, buffer_length)); + TEST_CHECK(inst->message.parsed == prev_parsed); + inst->message.data[prev_parsed + 2] ^= 0x80; + } + TEST_CHECK(get_record(&inst->message, NULL, NULL, NULL, buffer, buffer_length)); + TEST_CHECK(inst->message.parsed > prev_parsed); + } + + TEST_CHECK(!NKSN_GetRecord(inst, &critical, &t, &length, buffer, sizeof (buffer))); + + TEST_CHECK(NKSN_GetKeys(inst, AEAD_AES_SIV_CMAC_256, &c2s, &s2c)); + TEST_CHECK(c2s.length == SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256)); + TEST_CHECK(s2c.length == SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256)); +} + +static int +handle_request(void *arg) +{ + NKSN_Instance server = arg; + + verify_message(server); + + request_received = 1; + + send_message(server); + + return 1; +} + +static int +handle_response(void *arg) +{ + NKSN_Instance client = arg; + + response_received = 1; + + verify_message(client); + + return 1; +} + +static void +check_finished(void *arg) +{ + DEBUG_LOG("checking for stopped sessions"); + if (!NKSN_IsStopped(server) || !NKSN_IsStopped(client)) { + SCH_AddTimeoutByDelay(0.001, check_finished, NULL); + return; + } + + SCH_QuitProgram(); +} + +void +test_unit(void) +{ + NKSN_Credentials client_cred, server_cred; + const char *cert, *key; + int sock_fds[2], i; + uint32_t cert_id; + + LCL_Initialise(); + TST_RegisterDummyDrivers(); + + cert = "nts_ke.crt"; + key = "nts_ke.key"; + cert_id = 0; + + for (i = 0; i < 50; i++) { + SCH_Initialise(); + + server = NKSN_CreateInstance(1, NULL, handle_request, NULL); + client = NKSN_CreateInstance(0, "test", handle_response, NULL); + + server_cred = NKSN_CreateServerCertCredentials(&cert, &key, 1); + client_cred = NKSN_CreateClientCertCredentials(&cert, &cert_id, 1, 0); + + TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds) == 0); + TEST_CHECK(fcntl(sock_fds[0], F_SETFL, O_NONBLOCK) == 0); + TEST_CHECK(fcntl(sock_fds[1], F_SETFL, O_NONBLOCK) == 0); + + TEST_CHECK(NKSN_StartSession(server, sock_fds[0], "client", server_cred, 4.0)); + TEST_CHECK(NKSN_StartSession(client, sock_fds[1], "server", client_cred, 4.0)); + + send_message(client); + + request_received = response_received = 0; + + check_finished(NULL); + + SCH_MainLoop(); + + TEST_CHECK(NKSN_IsStopped(server)); + TEST_CHECK(NKSN_IsStopped(client)); + + TEST_CHECK(request_received); + TEST_CHECK(response_received); + + NKSN_DestroyInstance(server); + NKSN_DestroyInstance(client); + + NKSN_DestroyCertCredentials(server_cred); + NKSN_DestroyCertCredentials(client_cred); + + SCH_Finalise(); + } + + LCL_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ntp_auth.c b/test/unit/nts_ntp_auth.c new file mode 100644 index 0000000..307b93b --- /dev/null +++ b/test/unit/nts_ntp_auth.c @@ -0,0 +1,112 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#ifdef FEAT_NTS + +#include <nts_ntp_auth.c> + +#include "ntp_ext.h" +#include "siv.h" + +void +test_unit(void) +{ + unsigned char key[SIV_MAX_KEY_LENGTH], nonce[256], plaintext[256], plaintext2[256]; + NTP_PacketInfo info; + NTP_Packet packet; + SIV_Instance siv; + int i, j, r, packet_length, nonce_length, key_length; + int plaintext_length, plaintext2_length, min_ef_length; + + siv = SIV_CreateInstance(AEAD_AES_SIV_CMAC_256); + TEST_CHECK(siv); + + for (i = 0; i < 10000; i++) { + key_length = SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256); + for (j = 0; j < key_length; j++) + key[j] = random() % 256; + TEST_CHECK(SIV_SetKey(siv, key, key_length)); + + nonce_length = random() % sizeof (nonce) + 1; + for (j = 0; j < nonce_length; j++) + nonce[j] = random() % 256; + + plaintext_length = random() % (sizeof (plaintext) + 1); + for (j = 0; j < plaintext_length; j++) + plaintext[j] = random() % 256; + + packet_length = NTP_HEADER_LENGTH + random() % 100 * 4; + min_ef_length = random() % (sizeof (packet) - packet_length); + + memset(&packet, 0, sizeof (packet)); + packet.lvm = NTP_LVM(0, 4, 0); + memset(&info, 0, sizeof (info)); + info.version = 4; + info.length = packet_length; + + DEBUG_LOG("packet_length=%d nonce_length=%d plaintext_length=%d min_ef_length=%d", + packet_length, nonce_length, plaintext_length, min_ef_length); + + r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, nonce_length, plaintext, + -1, 0); + TEST_CHECK(!r); + r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, 0, plaintext, + plaintext_length, 0); + TEST_CHECK(!r); + r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, nonce_length, plaintext, + plaintext_length, sizeof (packet) - info.length + 1); + TEST_CHECK(!r); + + r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, nonce_length, plaintext, + plaintext_length, min_ef_length); + TEST_CHECK(r); + TEST_CHECK(info.length - packet_length >= min_ef_length); + + r = NNA_DecryptAuthEF(&packet, &info, siv, packet_length, plaintext2, + -1, &plaintext2_length); + TEST_CHECK(!r); + + r = NNA_DecryptAuthEF(&packet, &info, siv, packet_length, plaintext2, + sizeof (plaintext2), &plaintext2_length); + TEST_CHECK(r); + TEST_CHECK(plaintext_length == plaintext2_length); + TEST_CHECK(memcmp(plaintext, plaintext2, plaintext_length) == 0); + + j = random() % (packet_length + plaintext_length + + nonce_length + SIV_GetTagLength(siv) + 8) / 4 * 4; + ((unsigned char *)&packet)[j]++; + r = NNA_DecryptAuthEF(&packet, &info, siv, packet_length, plaintext2, + sizeof (plaintext2), &plaintext2_length); + TEST_CHECK(!r); + ((unsigned char *)&packet)[j]--; + } + + SIV_DestroyInstance(siv); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ntp_client.c b/test/unit/nts_ntp_client.c new file mode 100644 index 0000000..c14fd41 --- /dev/null +++ b/test/unit/nts_ntp_client.c @@ -0,0 +1,284 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#ifdef FEAT_NTS + +#include "socket.h" +#include "ntp.h" +#include "nts_ke_client.h" + +#define NKC_CreateInstance(address, name, cert_set) Malloc(1) +#define NKC_DestroyInstance(inst) Free(inst) +#define NKC_Start(inst) (random() % 2) +#define NKC_IsActive(inst) (random() % 2) +#define NKC_GetRetryFactor(inst) (1) + +static int get_nts_data(NKC_Instance inst, NKE_Context *context, + NKE_Cookie *cookies, int *num_cookies, int max_cookies, + IPSockAddr *ntp_address); +#define NKC_GetNtsData get_nts_data + +#include <nts_ntp_client.c> + +static int +get_nts_data(NKC_Instance inst, NKE_Context *context, + NKE_Cookie *cookies, int *num_cookies, int max_cookies, + IPSockAddr *ntp_address) +{ + int i; + + if (random() % 2) + return 0; + + context->algorithm = AEAD_AES_SIV_CMAC_256; + + context->c2s.length = SIV_GetKeyLength(context->algorithm); + UTI_GetRandomBytes(context->c2s.key, context->c2s.length); + context->s2c.length = SIV_GetKeyLength(context->algorithm); + UTI_GetRandomBytes(context->s2c.key, context->s2c.length); + + *num_cookies = random() % max_cookies + 1; + for (i = 0; i < *num_cookies; i++) { + cookies[i].length = random() % (sizeof (cookies[i].cookie) + 1); + if (random() % 4 != 0) + cookies[i].length = cookies[i].length / 4 * 4; + memset(cookies[i].cookie, random(), cookies[i].length); + } + + ntp_address->ip_addr.family = IPADDR_UNSPEC; + ntp_address->port = 0; + + return 1; +} + +static int +get_request(NNC_Instance inst) +{ + unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH], uniq_id[NTS_MIN_UNIQ_ID_LENGTH]; + NTP_PacketInfo info; + NTP_Packet packet; + int expected_length, req_cookies; + + memset(&packet, 0, sizeof (packet)); + memset(&info, 0, sizeof (info)); + info.version = 4; + info.mode = MODE_CLIENT; + info.length = random() % (sizeof (packet) + 1); + if (random() % 4 != 0) + info.length = info.length / 4 * 4; + + if (inst->num_cookies > 0 && random() % 2) { + inst->num_cookies = 0; + + TEST_CHECK(!NNC_GenerateRequestAuth(inst, &packet, &info)); + } + + while (!NNC_PrepareForAuth(inst)) { + inst->next_nke_attempt = SCH_GetLastEventMonoTime() + random() % 10 - 7; + } + + TEST_CHECK(inst->num_cookies > 0); + TEST_CHECK(inst->siv); + + memcpy(nonce, inst->nonce, sizeof (nonce)); + memcpy(uniq_id, inst->uniq_id, sizeof (uniq_id)); + TEST_CHECK(NNC_PrepareForAuth(inst)); + TEST_CHECK(memcmp(nonce, inst->nonce, sizeof (nonce)) != 0); + TEST_CHECK(memcmp(uniq_id, inst->uniq_id, sizeof (uniq_id)) != 0); + + req_cookies = MIN(NTS_MAX_COOKIES - inst->num_cookies + 1, + MAX_TOTAL_COOKIE_LENGTH / + (inst->cookies[inst->cookie_index].length + 4)); + expected_length = info.length + 4 + sizeof (inst->uniq_id) + + req_cookies * (4 + inst->cookies[inst->cookie_index].length) + + 4 + 4 + sizeof (inst->nonce) + SIV_GetTagLength(inst->siv); + DEBUG_LOG("length=%d cookie_length=%d expected_length=%d", + info.length, inst->cookies[inst->cookie_index].length, expected_length); + + if (info.length % 4 == 0 && info.length >= NTP_HEADER_LENGTH && + inst->cookies[inst->cookie_index].length % 4 == 0 && + inst->cookies[inst->cookie_index].length >= (NTP_MIN_EF_LENGTH - 4) && + expected_length <= sizeof (packet)) { + TEST_CHECK(NNC_GenerateRequestAuth(inst, &packet, &info)); + TEST_CHECK(info.length == expected_length); + return 1; + } else { + TEST_CHECK(!NNC_GenerateRequestAuth(inst, &packet, &info)); + return 0; + } +} + +static void +prepare_response(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info, int valid, int nak) +{ + unsigned char cookie[508], plaintext[528], nonce[448]; + int nonce_length, ef_length, cookie_length, plaintext_length, min_auth_length; + int i, index, auth_start; + SIV_Instance siv; + + memset(packet, 0, sizeof (*packet)); + packet->lvm = NTP_LVM(0, 4, MODE_SERVER); + memset(info, 0, sizeof (*info)); + info->version = 4; + info->mode = MODE_SERVER; + info->length = NTP_HEADER_LENGTH; + + if (valid) + index = -1; + else + index = random() % (nak ? 2 : 8); + + DEBUG_LOG("index=%d nak=%d", index, nak); + + if (index != 0) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_UNIQUE_IDENTIFIER, inst->uniq_id, + sizeof (inst->uniq_id))); + if (index == 1) + ((unsigned char *)packet)[NTP_HEADER_LENGTH + 4]++; + + if (nak) { + packet->stratum = NTP_INVALID_STRATUM; + packet->reference_id = htonl(NTP_KOD_NTS_NAK); + return; + } + + nonce_length = random() % (sizeof (nonce)) + 1; + + do { + cookie_length = random() % (sizeof (cookie) + 1); + } while (cookie_length % 4 != 0 || + ((index != 2) == (cookie_length < NTP_MIN_EF_LENGTH - 4 || + cookie_length > NKE_MAX_COOKIE_LENGTH))); + + min_auth_length = random() % (512 + 1); + + DEBUG_LOG("nonce_length=%d cookie_length=%d min_auth_length=%d", + nonce_length, cookie_length, min_auth_length); + + UTI_GetRandomBytes(nonce, nonce_length); + UTI_GetRandomBytes(cookie, cookie_length); + + if (cookie_length >= 12 && cookie_length <= 32 && random() % 2 == 0) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_COOKIE, cookie, cookie_length)); + + plaintext_length = 0; + if (index != 3) { + for (i = random() % ((sizeof (plaintext) - 16) / (cookie_length + 4)); i >= 0; i--) { + TEST_CHECK(NEF_SetField(plaintext, sizeof (plaintext), plaintext_length, + NTP_EF_NTS_COOKIE, cookie, + i == 0 ? cookie_length : random() % (cookie_length + 1) / 4 * 4, + &ef_length)); + plaintext_length += ef_length; + } + } + auth_start = info->length; + if (index != 4) { + if (index == 5) { + assert(plaintext_length + 16 <= sizeof (plaintext)); + memset(plaintext + plaintext_length, 0, 16); + plaintext_length += 16; + } + siv = SIV_CreateInstance(inst->context.algorithm); + TEST_CHECK(siv); + TEST_CHECK(SIV_SetKey(siv, inst->context.s2c.key, inst->context.s2c.length)); + TEST_CHECK(NNA_GenerateAuthEF(packet, info, siv, + nonce, nonce_length, plaintext, plaintext_length, + min_auth_length)); + SIV_DestroyInstance(siv); + } + if (index == 6) + ((unsigned char *)packet)[auth_start + 8]++; + if (index == 7) + TEST_CHECK(NEF_AddField(packet, info, 0x7000, inst->uniq_id, sizeof (inst->uniq_id))); +} + +void +test_unit(void) +{ + NNC_Instance inst; + NTP_PacketInfo info; + NTP_Packet packet; + IPSockAddr addr; + IPAddr ip_addr; + int i, j, prev_num_cookies, valid; + + TEST_CHECK(SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256) > 0); + + SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr); + addr.port = 0; + + inst = NNC_CreateInstance(&addr, "test", 0, 0); + TEST_CHECK(inst); + + for (i = 0; i < 100000; i++) { + if (!get_request(inst)) + continue; + + valid = random() % 2; + + TEST_CHECK(!inst->nak_response); + TEST_CHECK(!inst->ok_response); + + if (random() % 2) { + prepare_response(inst, &packet, &info, 0, 1); + TEST_CHECK(!NNC_CheckResponseAuth(inst, &packet, &info)); + TEST_CHECK(!inst->nak_response); + TEST_CHECK(!inst->ok_response); + for (j = random() % 3; j > 0; j--) { + prepare_response(inst, &packet, &info, 1, 1); + TEST_CHECK(!NNC_CheckResponseAuth(inst, &packet, &info)); + TEST_CHECK(inst->nak_response); + TEST_CHECK(!inst->ok_response); + } + } + + prev_num_cookies = inst->num_cookies; + prepare_response(inst, &packet, &info, valid, 0); + + if (valid) { + TEST_CHECK(NNC_CheckResponseAuth(inst, &packet, &info)); + TEST_CHECK(inst->num_cookies >= MIN(NTS_MAX_COOKIES, prev_num_cookies + 1)); + TEST_CHECK(inst->ok_response); + } + + prev_num_cookies = inst->num_cookies; + TEST_CHECK(!NNC_CheckResponseAuth(inst, &packet, &info)); + TEST_CHECK(inst->num_cookies == prev_num_cookies); + TEST_CHECK(inst->ok_response == valid); + + if (random() % 10 == 0) { + TST_GetRandomAddress(&ip_addr, IPADDR_INET4, 32); + NNC_ChangeAddress(inst, &ip_addr); + TEST_CHECK(UTI_CompareIPs(&inst->nts_address.ip_addr, &ip_addr, NULL) == 0); + } + } + + NNC_DestroyInstance(inst); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/nts_ntp_server.c b/test/unit/nts_ntp_server.c new file mode 100644 index 0000000..40938d6 --- /dev/null +++ b/test/unit/nts_ntp_server.c @@ -0,0 +1,176 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include "test.h" + +#ifdef FEAT_NTS + +#include <local.h> +#include <sched.h> + +#include <nts_ntp_server.c> + +static void +prepare_request(NTP_Packet *packet, NTP_PacketInfo *info, int valid, int nak) +{ + unsigned char uniq_id[NTS_MIN_UNIQ_ID_LENGTH], nonce[NTS_MIN_UNPADDED_NONCE_LENGTH]; + SIV_Instance siv; + NKE_Context context; + NKE_Cookie cookie; + int i, index, cookie_start, auth_start; + + context.algorithm = SERVER_SIV; + context.c2s.length = SIV_GetKeyLength(context.algorithm); + UTI_GetRandomBytes(&context.c2s.key, context.c2s.length); + context.s2c.length = SIV_GetKeyLength(context.algorithm); + UTI_GetRandomBytes(&context.s2c.key, context.s2c.length); + + TEST_CHECK(NKS_GenerateCookie(&context, &cookie)); + + UTI_GetRandomBytes(uniq_id, sizeof (uniq_id)); + UTI_GetRandomBytes(nonce, sizeof (nonce)); + + memset(packet, 0, sizeof (*packet)); + packet->lvm = NTP_LVM(0, 4, MODE_CLIENT); + memset(info, 0, sizeof (*info)); + info->version = 4; + info->mode = MODE_CLIENT; + info->length = NTP_HEADER_LENGTH; + + if (valid) + index = -1; + else + index = random() % 3; + + DEBUG_LOG("valid=%d nak=%d index=%d", valid, nak, index); + + if (index != 0) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_UNIQUE_IDENTIFIER, + uniq_id, sizeof (uniq_id))); + + cookie_start = info->length; + + if (index != 1) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_COOKIE, + cookie.cookie, cookie.length)); + + for (i = random() % 4; i > 0; i--) + TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_COOKIE_PLACEHOLDER, + cookie.cookie, cookie.length)); + + auth_start = info->length; + + if (index != 2) { + siv = SIV_CreateInstance(context.algorithm); + TEST_CHECK(SIV_SetKey(siv, context.c2s.key, context.c2s.length)); + TEST_CHECK(NNA_GenerateAuthEF(packet, info, siv, nonce, sizeof (nonce), + (const unsigned char *)"", 0, 0)); + SIV_DestroyInstance(siv); + } + + if (nak) + ((unsigned char *)packet)[(index == 2 ? cookie_start : + (index == 1 ? auth_start : + (random() % 2 ? cookie_start : auth_start))) + + 4 + random() % 16]++; +} + +static void +init_response(NTP_Packet *packet, NTP_PacketInfo *info) +{ + memset(packet, 0, sizeof (*packet)); + packet->lvm = NTP_LVM(0, 4, MODE_SERVER); + memset(info, 0, sizeof (*info)); + info->version = 4; + info->mode = MODE_SERVER; + info->length = NTP_HEADER_LENGTH; +} + +void +test_unit(void) +{ + NTP_PacketInfo req_info, res_info; + NTP_Packet request, response; + int i, valid, nak; + uint32_t kod; + + char conf[][100] = { + "ntsport 0", + "ntsprocesses 0", + "ntsserverkey nts_ke.key", + "ntsservercert nts_ke.crt", + }; + + CNF_Initialise(0, 0); + for (i = 0; i < sizeof conf / sizeof conf[0]; i++) + CNF_ParseLine(NULL, i + 1, conf[i]); + + LCL_Initialise(); + TST_RegisterDummyDrivers(); + SCH_Initialise(); + NKS_PreInitialise(0, 0, 0); + NKS_Initialise(); + NNS_Initialise(); + + for (i = 0; i < 50000; i++) { + valid = random() % 2; + nak = random() % 2; + prepare_request(&request, &req_info, valid, nak); + + TEST_CHECK(NNS_CheckRequestAuth(&request, &req_info, &kod) == (valid && !nak)); + + if (valid && !nak) { + TEST_CHECK(kod == 0); + TEST_CHECK(server->num_cookies > 0); + + init_response(&response, &res_info); + TEST_CHECK(NNS_GenerateResponseAuth(&request, &req_info, &response, &res_info, kod)); + + TEST_CHECK(res_info.ext_fields == 2); + TEST_CHECK(server->num_cookies == 0); + } else if (valid && nak) { + TEST_CHECK(kod == NTP_KOD_NTS_NAK); + TEST_CHECK(server->num_cookies == 0); + + init_response(&response, &res_info); + TEST_CHECK(NNS_GenerateResponseAuth(&request, &req_info, &response, &res_info, kod)); + + TEST_CHECK(res_info.ext_fields == 1); + TEST_CHECK(server->num_cookies == 0); + } else { + TEST_CHECK(kod == 0); + TEST_CHECK(server->num_cookies == 0); + } + } + + NNS_Finalise(); + NKS_Finalise(); + SCH_Finalise(); + LCL_Finalise(); + CNF_Finalise(); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/quantiles.c b/test/unit/quantiles.c new file mode 100644 index 0000000..5d97df6 --- /dev/null +++ b/test/unit/quantiles.c @@ -0,0 +1,68 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2022 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <local.h> +#include "test.h" + +#include <quantiles.c> + +void +test_unit(void) +{ + int i, j, k, min_k, max_k, q, r, in_order, out_order; + QNT_Instance inst; + double x; + + in_order = out_order = 0; + + for (i = 0; i < 100; i++) { + r = random() % 10 + 1; + q = random() % 20 + 2; + do { + min_k = random() % (q - 1) + 1; + max_k = random() % (q - 1) + 1; + } while (min_k > max_k); + + inst = QNT_CreateInstance(min_k, max_k, q, r, 1e-9); + + TEST_CHECK(min_k == QNT_GetMinK(inst)); + + for (j = 0; j < 3000; j++) { + x = TST_GetRandomDouble(0.0, 2e-6); + QNT_Accumulate(inst, x); + for (k = min_k; k < max_k; k++) + if (j < max_k - min_k) { + TEST_CHECK(QNT_GetQuantile(inst, k) <= QNT_GetQuantile(inst, k + 1)); + } else if (j > 1000) { + if (QNT_GetQuantile(inst, k) <= QNT_GetQuantile(inst, k + 1)) + in_order++; + else + out_order++; + } + } + + QNT_Reset(inst); + TEST_CHECK(inst->n_set == 0); + + QNT_DestroyInstance(inst); + } + + TEST_CHECK(in_order > 100 * out_order); +} diff --git a/test/unit/regress.c b/test/unit/regress.c new file mode 100644 index 0000000..f47d1c4 --- /dev/null +++ b/test/unit/regress.c @@ -0,0 +1,119 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2017 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ +#include <regress.c> +#include "test.h" + +#define POINTS 64 + +void +test_unit(void) +{ + double x[POINTS], x2[POINTS], y[POINTS], w[POINTS]; + double b0, b1, b2, s2, sb0, sb1, slope, slope2, intercept, sd, median; + double xrange, yrange, wrange, x2range; + int i, j, n, m, c1, c2, c3, runs, best_start, dof; + + for (n = 3; n <= POINTS; n++) { + for (i = 0; i < 200; i++) { + slope = TST_GetRandomDouble(-0.1, 0.1); + intercept = TST_GetRandomDouble(-1.0, 1.0); + sd = TST_GetRandomDouble(1e-6, 1e-4); + slope2 = (random() % 2 ? 1 : -1) * TST_GetRandomDouble(0.1, 0.5); + + DEBUG_LOG("iteration %d n=%d intercept=%e slope=%e sd=%e", + i, n, intercept, slope, sd); + + for (j = 0; j < n; j++) { + x[j] = -j; + y[j] = intercept + slope * x[j] + (j % 2 ? 1 : -1) * TST_GetRandomDouble(1e-6, sd); + w[j] = TST_GetRandomDouble(1.0, 2.0); + x2[j] = (y[j] - intercept - slope * x[j]) / slope2; + } + + RGR_WeightedRegression(x, y, w, n, &b0, &b1, &s2, &sb0, &sb1); + DEBUG_LOG("WR b0=%e b1=%e s2=%e sb0=%e sb1=%e", b0, b1, s2, sb0, sb1); + TEST_CHECK(fabs(b0 - intercept) < sd + 1e-3); + TEST_CHECK(fabs(b1 - slope) < sd); + + if (RGR_FindBestRegression(x, y, w, n, 0, 3, &b0, &b1, &s2, &sb0, &sb1, + &best_start, &runs, &dof)) { + DEBUG_LOG("BR b0=%e b1=%e s2=%e sb0=%e sb1=%e runs=%d bs=%d dof=%d", + b0, b1, s2, sb0, sb1, runs, best_start, dof); + + TEST_CHECK(fabs(b0 - intercept) < sd + 1e-3); + TEST_CHECK(fabs(b1 - slope) < sd); + } + + if (RGR_MultipleRegress(x, x2, y, n, &b2)) { + DEBUG_LOG("MR b2=%e", b2); + TEST_CHECK(fabs(b2 - slope2) < 1e-6); + } + + for (j = 0; j < n / 7; j++) + y[random() % n] += 100 * sd; + + if (RGR_FindBestRobustRegression(x, y, n, 1e-8, &b0, &b1, &runs, &best_start)) { + DEBUG_LOG("BRR b0=%e b1=%e runs=%d bs=%d", b0, b1, runs, best_start); + + TEST_CHECK(fabs(b0 - intercept) < sd + 1e-2); + TEST_CHECK(fabs(b1 - slope) < 5.0 * sd); + } + + for (j = 0; j < n; j++) + x[j] = random() % 4 * TST_GetRandomDouble(-1000, 1000); + + median = RGR_FindMedian(x, n); + + for (j = c1 = c2 = c3 = 0; j < n; j++) { + if (x[j] < median) + c1++; + if (x[j] > median) + c3++; + else + c2++; + } + + TEST_CHECK(c1 + c2 >= c3 && c1 <= c2 + c3); + + xrange = TST_GetRandomDouble(1e-6, pow(10.0, random() % 10)); + yrange = random() % 3 * TST_GetRandomDouble(0.0, pow(10.0, random() % 10)); + wrange = random() % 3 * TST_GetRandomDouble(0.0, pow(10.0, random() % 10)); + x2range = random() % 3 * TST_GetRandomDouble(0.0, pow(10.0, random() % 10)); + m = random() % n; + + for (j = 0; j < n; j++) { + x[j] = (j ? x[j - 1] : 0.0) + TST_GetRandomDouble(1e-6, xrange); + y[j] = TST_GetRandomDouble(-yrange, yrange); + w[j] = 1.0 + TST_GetRandomDouble(0.0, wrange); + x2[j] = TST_GetRandomDouble(-x2range, x2range); + } + + RGR_WeightedRegression(x, y, w, n, &b0, &b1, &s2, &sb0, &sb1); + + if (RGR_FindBestRegression(x + m, y + m, w, n - m, m, 3, &b0, &b1, &s2, &sb0, &sb1, + &best_start, &runs, &dof)) + ; + if (RGR_MultipleRegress(x, x2, y, n, &b2)) + ; + if (RGR_FindBestRobustRegression(x, y, n, 1e-8, &b0, &b1, &runs, &best_start)) + ; + } + } +} diff --git a/test/unit/samplefilt.c b/test/unit/samplefilt.c new file mode 100644 index 0000000..19d2f70 --- /dev/null +++ b/test/unit/samplefilt.c @@ -0,0 +1,120 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <local.h> +#include "test.h" + +#define LCL_GetSysPrecisionAsQuantum() (1.0e-6) + +#include <samplefilt.c> + +void +test_unit(void) +{ + NTP_Sample sample_in, sample_out; + SPF_Instance filter; + int i, j, k, sum_count, min_samples, max_samples; + double mean, combine_ratio, sum_err; + + LCL_Initialise(); + + memset(&sample_in, 0, sizeof (sample_in)); + memset(&sample_out, 0, sizeof (sample_out)); + + for (i = 0; i <= 100; i++) { + max_samples = random() % 20 + 1; + min_samples = random() % (max_samples) + 1; + combine_ratio = TST_GetRandomDouble(0.0, 1.0); + + filter = SPF_CreateInstance(min_samples, max_samples, 2.0, combine_ratio); + + TEST_CHECK(max_samples == SPF_GetMaxSamples(filter)); + + for (j = 0, sum_count = 0, sum_err = 0.0; j < 100; j++) { + DEBUG_LOG("iteration %d/%d", i, j); + + mean = TST_GetRandomDouble(-1.0e3, 1.0e3); + UTI_ZeroTimespec(&sample_in.time); + + for (k = 0; k < 100; k++) { + UTI_AddDoubleToTimespec(&sample_in.time, TST_GetRandomDouble(1.0e-1, 1.0e2), + &sample_in.time); + sample_in.offset = mean + TST_GetRandomDouble(-1.0, 1.0); + sample_in.peer_dispersion = TST_GetRandomDouble(1.0e-4, 2.0e-4); + sample_in.root_dispersion = TST_GetRandomDouble(1.0e-3, 2.0e-3); + sample_in.peer_delay = TST_GetRandomDouble(1.0e-2, 2.0e-2); + sample_in.root_delay = TST_GetRandomDouble(1.0e-1, 2.0e-1); + + TEST_CHECK(SPF_AccumulateSample(filter, &sample_in)); + TEST_CHECK(!SPF_AccumulateSample(filter, &sample_in)); + + TEST_CHECK(SPF_GetNumberOfSamples(filter) == MIN(k + 1, max_samples)); + + SPF_GetLastSample(filter, &sample_out); + TEST_CHECK(!memcmp(&sample_in, &sample_out, sizeof (sample_in))); + + SPF_SlewSamples(filter, &sample_in.time, 0.0, 0.0); + SPF_CorrectOffset(filter, 0.0); + SPF_AddDispersion(filter, 0.0); + + if (k + 1 < min_samples) + TEST_CHECK(!SPF_GetFilteredSample(filter, &sample_out)); + + TEST_CHECK(SPF_GetNumberOfSamples(filter) == MIN(k + 1, max_samples)); + } + + if (random() % 10) { + TEST_CHECK(SPF_GetFilteredSample(filter, &sample_out)); + + TEST_CHECK(SPF_GetAvgSampleDispersion(filter) <= 2.0); + + sum_err += sample_out.offset - mean; + sum_count++; + + TEST_CHECK(UTI_CompareTimespecs(&sample_out.time, &sample_in.time) <= 0 && + sample_out.time.tv_sec >= 0); + TEST_CHECK(fabs(sample_out.offset - mean) <= 1.0); + TEST_CHECK(sample_out.peer_dispersion >= 1.0e-4 && + (sample_out.peer_dispersion <= 2.0e-4 || filter->max_samples > 1)); + TEST_CHECK(sample_out.root_dispersion >= 1.0e-3 && + (sample_out.root_dispersion <= 2.0e-3 || filter->max_samples > 1)); + TEST_CHECK(sample_out.peer_delay >= 1.0e-2 && + sample_out.peer_delay <= 2.0e-2); + TEST_CHECK(sample_out.root_delay >= 1.0e-1 && + sample_out.root_delay <= 2.0e-1); + + if (max_samples == 1) + TEST_CHECK(!memcmp(&sample_in, &sample_out, sizeof (sample_in))); + + } else { + SPF_DropSamples(filter); + TEST_CHECK(filter->last < 0); + } + + TEST_CHECK(SPF_GetNumberOfSamples(filter) == 0); + } + + TEST_CHECK(fabs(sum_err / sum_count) < 0.3); + + SPF_DestroyInstance(filter); + } + + LCL_Finalise(); +} diff --git a/test/unit/siv.c b/test/unit/siv.c new file mode 100644 index 0000000..76846c8 --- /dev/null +++ b/test/unit/siv.c @@ -0,0 +1,321 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2019 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include <sysincl.h> +#include <logging.h> +#include <siv.h> +#include "test.h" + +#ifdef HAVE_SIV + +struct siv_test { + SIV_Algorithm algorithm; + const unsigned char key[64]; + int key_length; + const unsigned char nonce[128]; + int nonce_length; + const unsigned char assoc[128]; + int assoc_length; + const unsigned char plaintext[128]; + int plaintext_length; + const unsigned char ciphertext[128]; + int ciphertext_length; +}; + +void +test_unit(void) +{ + struct siv_test tests[] = { + { AEAD_AES_SIV_CMAC_256, + "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde" + "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32, + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 16, + "", 0, + "", 0, + "\x22\x3e\xb5\x94\xe0\xe0\x25\x4b\x00\x25\x8e\x21\x9a\x1c\xa4\x21", 16 + }, + { AEAD_AES_SIV_CMAC_256, + "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde" + "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32, + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 16, + "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b", 16, + "", 0, + "\xd7\x20\x19\x89\xc6\xdb\xc6\xd6\x61\xfc\x62\xbc\x86\x5e\xee\xef", 16 + }, + { AEAD_AES_SIV_CMAC_256, + "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde" + "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32, + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 16, + "", 0, + "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b", 16, + "\xb6\xc1\x60\xe9\xc2\xfd\x2a\xe8\xde\xc5\x36\x8b\x2a\x33\xed\xe1" + "\x14\xff\xb3\x97\x34\x5c\xcb\xe4\x4a\xa4\xde\xac\xd9\x36\x90\x46", 32 + }, + { AEAD_AES_SIV_CMAC_256, + "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde" + "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32, + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e", 15, + "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c", 15, + "\xba\x99\x79\x31\x23\x7e\x3c\x53\x58\x7e\xd4\x93\x02\xab\xe4", 15, + "\x03\x8c\x41\x51\xba\x7a\x8f\x77\x6e\x56\x31\x99\x42\x0b\xc7\x03" + "\xe7\x6c\x67\xc9\xda\xb7\x0d\x5b\x44\x06\x26\x5a\xd0\xd2\x3b", 31 + }, + { AEAD_AES_SIV_CMAC_256, + "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde" + "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32, + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 16, + "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b", 16, + "\xba\x99\x79\x31\x23\x7e\x3c\x53\x58\x7e\xd4\x93\x02\xab\xe4\xa7", 16, + "\x5c\x05\x23\x65\xf4\x57\x0a\xa0\xfb\x38\x3e\xce\x9b\x75\x85\xeb" + "\x68\x85\x19\x36\x0c\x7c\x48\x11\x40\xcb\x9b\x57\x9a\x0e\x65\x32", 32 + }, + { AEAD_AES_SIV_CMAC_256, + "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde" + "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32, + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + "\xd5", 17, + "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b" + "\xa0", 17, + "\xba\x99\x79\x31\x23\x7e\x3c\x53\x58\x7e\xd4\x93\x02\xab\xe4\xa7" + "\x08", 17, + "\xaf\x58\x4b\xe7\x82\x1e\x96\x19\x29\x91\x25\xe0\xdd\x80\x3b\x49" + "\xa5\x11\xcd\xb6\x08\xf3\x76\xa0\xb6\xfa\x15\x82\xf3\x95\xe1\xeb" + "\xbd", 33 + }, + { AEAD_AES_SIV_CMAC_256, + "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde" + "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32, + "\xb0\x5a\x1b\xc7\x56\xe7\xb6\x2c\xb4\x85\xe5\x56\xa5\x28\xc0\x6c" + "\x2f\x3b\x0b\x9d\x1a\x0c\xdf\x69\x47\xe0\xcc\xc0\x87\xaa\x5c\x09" + "\x98\x48\x8d\x6a\x8e\x1e\x05\xd7\x8b\x68\x74\x83\xb5\x1d\xf1\x2c", 48, + "\xe5\x8b\xd2\x6a\x30\xc5\xc5\x61\xcc\xbd\x7c\x27\xbf\xfe\xf9\x06" + "\x00\x5b\xd7\xfc\x11\x0b\xcf\x16\x61\xef\xac\x05\xa7\xaf\xec\x27" + "\x41\xc8\x5e\x9e\x0d\xf9\x2f\xaf\x20\x79\x17\xe5\x17\x91\x2a\x27" + "\x34\x1c\xbc\xaf\xeb\xef\x7f\x52\xe7\x1e\x4c\x2a\xca\xbd\x2b\xbe" + "\x34\xd6\xfb\x69\xd3\x3e\x49\x59\x60\xb4\x26\xc9\xb8\xce\xba", 79, + "\x6c\xe7\xcf\x7e\xab\x7b\xa0\xe1\xa7\x22\xcb\x88\xde\x5e\x42\xd2" + "\xec\x79\xe0\xa2\xcf\x5f\x0f\x6f\x6b\x89\x57\xcd\xae\x17\xd4\xc2" + "\xf3\x1b\xa2\xa8\x13\x78\x23\x2f\x83\xa8\xd4\x0c\xc0\xd2\xf3\x99" + "\xae\x81\xa1\xca\x5b\x5f\x45\xa6\x6f\x0c\x8a\xf3\xd4\x67\x40\x81" + "\x26\xe2\x01\x86\xe8\x5a\xd5\xf8\x58\x80\x9f\x56\xaa\x76\x96\xbf" + "\x31", 81, + "\x9a\x06\x33\xe0\xee\x00\x6a\x9b\xc8\x20\xd5\xe2\xc2\xed\xb5\x75" + "\xfa\x9e\x42\x2a\x31\x6b\xda\xca\xaa\x7d\x31\x8b\x84\x7a\xb8\xd7" + "\x8a\x81\x25\x64\xed\x41\x9b\xa9\x77\x10\xbd\x05\x0c\x4e\xc5\x31" + "\x0c\xa2\x86\xec\x8a\x94\xc8\x24\x23\x3c\x13\xee\xa5\x51\xc9\xdf" + "\x48\xc9\x55\xc5\x2f\x40\x73\x3f\x98\xbb\x8d\x69\x78\x46\x64\x17" + "\x8d\x49\x2f\x14\x62\xa4\x7c\x2a\x57\x38\x87\xce\xc6\x72\xd3\x5c" + "\xa1", 97 + }, + { 0, "", 0 } + }; + + unsigned char plaintext[sizeof (((struct siv_test *)NULL)->plaintext)]; + unsigned char ciphertext[sizeof (((struct siv_test *)NULL)->ciphertext)]; + SIV_Instance siv; + int i, j, r; + + TEST_CHECK(SIV_CreateInstance(0) == NULL); + + for (i = 0; tests[i].algorithm != 0; i++) { + DEBUG_LOG("testing %d (%d)", (int)tests[i].algorithm, i); + + assert(tests[i].key_length <= sizeof (tests[i].key)); + assert(tests[i].nonce_length <= sizeof (tests[i].nonce)); + assert(tests[i].assoc_length <= sizeof (tests[i].assoc)); + assert(tests[i].plaintext_length <= sizeof (tests[i].plaintext)); + assert(tests[i].ciphertext_length <= sizeof (tests[i].ciphertext)); + + siv = SIV_CreateInstance(tests[i].algorithm); + TEST_CHECK(siv != NULL); + + TEST_CHECK(SIV_GetKeyLength(tests[i].algorithm) == tests[i].key_length); + + r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].plaintext, tests[i].plaintext_length, + ciphertext, tests[i].ciphertext_length); + TEST_CHECK(!r); + r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].ciphertext, tests[i].ciphertext_length, + plaintext, tests[i].plaintext_length); + TEST_CHECK(!r); + + for (j = -1; j < 1024; j++) { + r = SIV_SetKey(siv, tests[i].key, j); + TEST_CHECK(r == (j == tests[i].key_length)); + } + + TEST_CHECK(SIV_GetTagLength(siv) == tests[i].ciphertext_length - tests[i].plaintext_length); + + r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].plaintext, tests[i].plaintext_length, + ciphertext, tests[i].ciphertext_length); + TEST_CHECK(r); + +#if 0 + for (j = 0; j < tests[i].ciphertext_length; j++) { + printf("\\x%02x", ciphertext[j]); + if (j % 16 == 15) + printf("\n"); + } + printf("\n"); +#endif + TEST_CHECK(memcmp(ciphertext, tests[i].ciphertext, tests[i].ciphertext_length) == 0); + + for (j = -1; j < tests[i].nonce_length; j++) { + r = SIV_Encrypt(siv, tests[i].nonce, j, + tests[i].assoc, tests[i].assoc_length, + tests[i].plaintext, tests[i].plaintext_length, + ciphertext, tests[i].ciphertext_length); + if (j > 0) { + TEST_CHECK(r); + TEST_CHECK(memcmp(ciphertext, tests[i].ciphertext, tests[i].ciphertext_length) != 0); + } else { + TEST_CHECK(!r); + } + } + + for (j = -1; j < tests[i].assoc_length; j++) { + r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, j, + tests[i].plaintext, tests[i].plaintext_length, + ciphertext, tests[i].ciphertext_length); + if (j >= 0) { + TEST_CHECK(r); + TEST_CHECK(memcmp(ciphertext, tests[i].ciphertext, tests[i].ciphertext_length) != 0); + } else { + TEST_CHECK(!r); + } + } + + for (j = -1; j < tests[i].plaintext_length; j++) { + r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].plaintext, j, + ciphertext, j + SIV_GetTagLength(siv)); + if (j >= 0) { + TEST_CHECK(r); + TEST_CHECK(memcmp(ciphertext, tests[i].ciphertext, j + SIV_GetTagLength(siv)) != 0); + } else { + TEST_CHECK(!r); + } + } + + for (j = -1; j < 2 * tests[i].plaintext_length; j++) { + if (j == tests[i].plaintext_length) + continue; + r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].plaintext, j, + ciphertext, tests[i].ciphertext_length); + TEST_CHECK(!r); + } + + for (j = -1; j < 2 * tests[i].ciphertext_length; j++) { + if (j == tests[i].ciphertext_length) + continue; + r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].plaintext, tests[i].plaintext_length, + ciphertext, j); + TEST_CHECK(!r); + } + + r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].ciphertext, tests[i].ciphertext_length, + plaintext, tests[i].plaintext_length); + TEST_CHECK(r); + TEST_CHECK(memcmp(plaintext, tests[i].plaintext, tests[i].plaintext_length) == 0); + + for (j = -1; j < tests[i].nonce_length; j++) { + r = SIV_Decrypt(siv, tests[i].nonce, j, + tests[i].assoc, tests[i].assoc_length, + tests[i].ciphertext, tests[i].ciphertext_length, + plaintext, tests[i].plaintext_length); + TEST_CHECK(!r); + } + + for (j = -1; j < tests[i].assoc_length; j++) { + r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, j, + tests[i].ciphertext, tests[i].ciphertext_length, + plaintext, tests[i].plaintext_length); + TEST_CHECK(!r); + } + + for (j = -1; j < 2 * tests[i].ciphertext_length; j++) { + if (j == tests[i].ciphertext_length) + continue; + + r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].ciphertext, j, + plaintext, tests[i].plaintext_length); + TEST_CHECK(!r); + } + + for (j = -1; j < tests[i].plaintext_length; j++) { + r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].ciphertext, tests[i].ciphertext_length, + plaintext, j); + TEST_CHECK(!r); + + r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length, + tests[i].assoc, tests[i].assoc_length, + tests[i].ciphertext, j + SIV_GetTagLength(siv), + plaintext, j); + TEST_CHECK(!r); + } + + SIV_DestroyInstance(siv); + } + + siv = SIV_CreateInstance(tests[0].algorithm); + for (i = 0; i < 1000; i++) { + for (j = 0; tests[j].algorithm == tests[0].algorithm; j++) { + r = SIV_SetKey(siv, tests[j].key, tests[j].key_length); + TEST_CHECK(r); + r = SIV_Encrypt(siv, tests[j].nonce, tests[j].nonce_length, + tests[j].assoc, tests[j].assoc_length, + tests[j].plaintext, tests[j].plaintext_length, + ciphertext, tests[j].ciphertext_length); + TEST_CHECK(r); + r = SIV_Decrypt(siv, tests[j].nonce, tests[j].nonce_length, + tests[j].assoc, tests[j].assoc_length, + tests[j].ciphertext, tests[j].ciphertext_length, + plaintext, tests[j].plaintext_length); + TEST_CHECK(r); + } + } + SIV_DestroyInstance(siv); +} +#else +void +test_unit(void) +{ + TEST_REQUIRE(0); +} +#endif diff --git a/test/unit/smooth.c b/test/unit/smooth.c new file mode 100644 index 0000000..998a4d1 --- /dev/null +++ b/test/unit/smooth.c @@ -0,0 +1,63 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <smooth.c> +#include "test.h" + +void +test_unit(void) +{ + int i, j; + struct timespec ts; + double offset, freq, wander; + char conf[] = "smoothtime 300 0.01"; + + CNF_Initialise(0, 0); + CNF_ParseLine(NULL, 1, conf); + + LCL_Initialise(); + SMT_Initialise(); + locked = 0; + + for (i = 0; i < 500; i++) { + UTI_ZeroTimespec(&ts); + SMT_Reset(&ts); + + DEBUG_LOG("iteration %d", i); + + offset = (random() % 1000000 - 500000) / 1.0e6; + freq = (random() % 1000000 - 500000) / 1.0e9; + update_smoothing(&ts, offset, freq); + + for (j = 0; j < 10000; j++) { + update_smoothing(&ts, 0.0, 0.0); + UTI_AddDoubleToTimespec(&ts, 16.0, &ts); + get_smoothing(&ts, &offset, &freq, &wander); + } + + TEST_CHECK(fabs(offset) < 1e-12); + TEST_CHECK(fabs(freq) < 1e-12); + TEST_CHECK(fabs(wander) < 1e-12); + } + + SMT_Finalise(); + LCL_Finalise(); + CNF_Finalise(); +} diff --git a/test/unit/sources.c b/test/unit/sources.c new file mode 100644 index 0000000..155e819 --- /dev/null +++ b/test/unit/sources.c @@ -0,0 +1,289 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2016, 2018, 2022 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <sources.c> +#include "test.h" + +static SRC_Instance +create_source(SRC_Type type, IPAddr *addr, int authenticated, int sel_options) +{ + TST_GetRandomAddress(addr, IPADDR_UNSPEC, -1); + + return SRC_CreateNewInstance(UTI_IPToRefid(addr), type, authenticated, sel_options, + type == SRC_NTP ? addr : NULL, + SRC_DEFAULT_MINSAMPLES, SRC_DEFAULT_MAXSAMPLES, 0.0, 1.0); +} + +void +test_unit(void) +{ + SRC_AuthSelectMode sel_mode; + SRC_Instance srcs[16]; + IPAddr addrs[16]; + RPT_SourceReport report; + NTP_Sample sample; + int i, j, k, l, n1, n2, n3, n4, samples, sel_options; + char conf[128]; + + CNF_Initialise(0, 0); + LCL_Initialise(); + TST_RegisterDummyDrivers(); + SCH_Initialise(); + SRC_Initialise(); + REF_Initialise(); + NSR_Initialise(); + + REF_SetMode(REF_ModeIgnore); + + for (i = 0; i < 1000; i++) { + DEBUG_LOG("iteration %d", i); + + for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) { + TEST_CHECK(n_sources == j); + + sel_options = i & random() & (SRC_SELECT_NOSELECT | SRC_SELECT_PREFER | + SRC_SELECT_TRUST | SRC_SELECT_REQUIRE); + + DEBUG_LOG("added source %d options %d", j, sel_options); + srcs[j] = create_source(SRC_NTP, &addrs[j], 0, sel_options); + SRC_UpdateReachability(srcs[j], 1); + + samples = (i + j) % 5 + 3; + + sample.offset = TST_GetRandomDouble(-1.0, 1.0); + + for (k = 0; k < samples; k++) { + SCH_GetLastEventTime(&sample.time, NULL, NULL); + UTI_AddDoubleToTimespec(&sample.time, TST_GetRandomDouble(k - samples, k - samples + 1), + &sample.time); + + sample.offset += TST_GetRandomDouble(-1.0e-2, 1.0e-2); + sample.peer_delay = TST_GetRandomDouble(1.0e-6, 1.0e-1); + sample.peer_dispersion = TST_GetRandomDouble(1.0e-6, 1.0e-1); + sample.root_delay = sample.peer_delay; + sample.root_dispersion = sample.peer_dispersion; + + if (random() % 2) + SRC_UpdateStatus(srcs[j], 1, random() % 4); + + DEBUG_LOG("source %d sample %d offset %f delay %f disp %f", j, k, + sample.offset, sample.peer_delay, sample.peer_dispersion); + + SRC_AccumulateSample(srcs[j], &sample); + } + + for (k = 0; k <= j; k++) { + int passed = 0, trusted = 0, trusted_passed = 0, required = 0, required_passed = 0; + double trusted_lo = DBL_MAX, trusted_hi = DBL_MIN; + double passed_lo = DBL_MAX, passed_hi = DBL_MIN; + + SRC_SelectSource(srcs[k]); + DEBUG_LOG("source %d status %c", k, get_status_char(sources[k]->status)); + + for (l = 0; l <= j; l++) { + TEST_CHECK(sources[l]->status > SRC_OK && sources[l]->status <= SRC_SELECTED); + if (sources[l]->sel_options & SRC_SELECT_NOSELECT) { + TEST_CHECK(sources[l]->status == SRC_UNSELECTABLE); + } else if (sources[l]->leap == LEAP_Unsynchronised) { + TEST_CHECK(sources[l]->status == SRC_UNSYNCHRONISED); + } else if (sources[l]->status != SRC_BAD_DISTANCE) { + if (sources[l]->status >= SRC_NONPREFERRED) { + passed++; + if (passed_lo > sources[l]->sel_info.lo_limit) + passed_lo = sources[l]->sel_info.lo_limit; + if (passed_hi < sources[l]->sel_info.hi_limit) + passed_hi = sources[l]->sel_info.hi_limit; + } + if (sources[l]->sel_options & SRC_SELECT_TRUST) { + trusted++; + if (trusted_lo > sources[l]->sel_info.lo_limit) + trusted_lo = sources[l]->sel_info.lo_limit; + if (trusted_hi < sources[l]->sel_info.hi_limit) + trusted_hi = sources[l]->sel_info.hi_limit; + if (sources[l]->status >= SRC_NONPREFERRED) + trusted_passed++; + } + if (sources[l]->sel_options & SRC_SELECT_REQUIRE) { + required++; + if (sources[l]->status >= SRC_NONPREFERRED) + required_passed++; + } + if (sources[l]->sel_options & SRC_SELECT_PREFER) + TEST_CHECK(sources[l]->status != SRC_NONPREFERRED); + } + } + + DEBUG_LOG("sources %d passed %d trusted %d/%d required %d/%d", j + 1, passed, + trusted_passed, trusted, required_passed, required); + + TEST_CHECK(!trusted || !passed || (passed_lo >= trusted_lo && passed_hi <= trusted_hi)); + TEST_CHECK(!passed || !trusted || trusted_passed >= 1); + TEST_CHECK(!passed || !required || required_passed > 0); + + for (l = 0; l <= j; l++) { + TEST_CHECK(sources[l]->leap_vote == + (sources[l]->status >= SRC_NONPREFERRED && + (!trusted || sources[l]->sel_options & SRC_SELECT_TRUST))); + } + } + } + + for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) { + SRC_ReportSource(j, &report, &sample.time); + SRC_DestroyInstance(srcs[j]); + } + } + + for (i = 0; i < 16; i++) { + DEBUG_LOG("iteration %d", i); + + for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) { + TEST_CHECK(n_sources == j); + + srcs[j] = create_source(SRC_NTP, &addrs[j], 0, 0); + SRC_UpdateReachability(srcs[j], 1); + + samples = 8; + for (k = 0; k < samples; k++) { + SCH_GetLastEventTime(&sample.time, NULL, NULL); + UTI_AddDoubleToTimespec(&sample.time, k - samples, &sample.time); + if (j != 0) + UTI_AddDoubleToTimespec(&sample.time, -i * (1.0e-3 / LCL_GetMaxClockError()), &sample.time); + + sample.offset = (k % 2) * 1e-8; + sample.peer_delay = sample.root_delay = 2.0e-3 * (j + 1); + sample.peer_dispersion = sample.root_dispersion = 4.0e-3; + + SRC_AccumulateSample(srcs[j], &sample); + SRC_UpdateStatus(srcs[j], 1, LEAP_Normal); + } + } + + SRC_SelectSource(srcs[0]); + + for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) { + DEBUG_LOG("%d %c %f", j, get_status_char(srcs[j]->status), + srcs[j]->sel_info.root_distance); + if (j == 0) + TEST_CHECK(sources[j]->status == SRC_SELECTED); + else if (j < 11 - i) + TEST_CHECK(sources[j]->status == SRC_UNSELECTED); + else + TEST_CHECK(sources[j]->status == SRC_DISTANT); + } + + for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) { + SCH_GetLastEventTime(&sample.time, NULL, NULL); + SRC_ReportSource(j, &report, &sample.time); + SRC_DestroyInstance(srcs[j]); + } + } + + TEST_CHECK(CNF_GetAuthSelectMode() == SRC_AUTHSELECT_MIX); + + for (i = 0; i < 1000; i++) { + DEBUG_LOG("iteration %d", i); + + switch (i % 4) { + case 0: + snprintf(conf, sizeof (conf), "authselectmode require"); + sel_mode = SRC_AUTHSELECT_REQUIRE; + break; + case 1: + snprintf(conf, sizeof (conf), "authselectmode prefer"); + sel_mode = SRC_AUTHSELECT_PREFER; + break; + case 2: + snprintf(conf, sizeof (conf), "authselectmode mix"); + sel_mode = SRC_AUTHSELECT_MIX; + break; + case 3: + snprintf(conf, sizeof (conf), "authselectmode ignore"); + sel_mode = SRC_AUTHSELECT_IGNORE; + break; + } + + CNF_ParseLine(NULL, 0, conf); + TEST_CHECK(CNF_GetAuthSelectMode() == sel_mode); + + sel_options = random() & (SRC_SELECT_PREFER | SRC_SELECT_TRUST | SRC_SELECT_REQUIRE); + + n1 = random() % 3; + n2 = random() % 3; + n3 = random() % 3; + n4 = random() % 3; + assert(n1 + n2 + n3 < sizeof (srcs) / sizeof (srcs[0])); + + for (j = 0; j < n1; j++) + srcs[j] = create_source(SRC_REFCLOCK, &addrs[j], random() % 2, sel_options); + for (; j < n1 + n2; j++) + srcs[j] = create_source(SRC_NTP, &addrs[j], 1, sel_options); + for (; j < n1 + n2 + n3; j++) + srcs[j] = create_source(SRC_NTP, &addrs[j], 0, sel_options); + for (; j < n1 + n2 + n3 + n4; j++) + srcs[j] = create_source(random() % 2 ? SRC_REFCLOCK : SRC_NTP, &addrs[j], + random() % 2, sel_options | SRC_SELECT_NOSELECT); + + switch (sel_mode) { + case SRC_AUTHSELECT_IGNORE: + for (j = 0; j < n1 + n2 + n3; j++) + TEST_CHECK(srcs[j]->sel_options == sel_options); + break; + case SRC_AUTHSELECT_MIX: + for (j = 0; j < n1 + n2; j++) + TEST_CHECK(srcs[j]->sel_options == + (sel_options | (n2 > 0 && n3 > 0 ? SRC_SELECT_REQUIRE | SRC_SELECT_TRUST : 0))); + for (; j < n1 + n2 + n3; j++) + TEST_CHECK(srcs[j]->sel_options == sel_options); + break; + case SRC_AUTHSELECT_PREFER: + for (j = 0; j < n1 + n2; j++) + TEST_CHECK(srcs[j]->sel_options == sel_options); + for (; j < n1 + n2 + n3; j++) + TEST_CHECK(srcs[j]->sel_options == (sel_options | (n2 > 0 ? SRC_SELECT_NOSELECT : 0))); + break; + case SRC_AUTHSELECT_REQUIRE: + for (j = 0; j < n1 + n2; j++) + TEST_CHECK(srcs[j]->sel_options == sel_options); + for (; j < n1 + n2 + n3; j++) + TEST_CHECK(srcs[j]->sel_options == (sel_options | SRC_SELECT_NOSELECT)); + break; + default: + assert(0); + } + + for (j = n1 + n2 + n3; j < n1 + n2 + n3 + n4; j++) + TEST_CHECK(srcs[j]->sel_options == (sel_options | SRC_SELECT_NOSELECT)); + + for (j = n1 + n2 + n3 + n4 - 1; j >= 0; j--) { + if (j < n1 + n2) + TEST_CHECK(srcs[j]->sel_options == sel_options); + SRC_DestroyInstance(srcs[j]); + } + } + + NSR_Finalise(); + REF_Finalise(); + SRC_Finalise(); + SCH_Finalise(); + LCL_Finalise(); + CNF_Finalise(); + HSH_Finalise(); +} diff --git a/test/unit/test.c b/test/unit/test.c new file mode 100644 index 0000000..94619c1 --- /dev/null +++ b/test/unit/test.c @@ -0,0 +1,181 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <config.h> +#include <sysincl.h> +#include <logging.h> +#include <localp.h> +#include <util.h> + +#include "test.h" + +void +TST_Fail(int line) +{ + printf("FAIL (on line %d)\n", line); + exit(1); +} + +void +TST_Skip(int line) +{ + printf("SKIP (on line %d)\n", line); + exit(0); +} + +int +main(int argc, char **argv) +{ + LOG_Severity log_severity; + char *test_name, *s; + int i, seed = 0; + struct timeval tv; + + test_name = argv[0]; + s = strrchr(test_name, '.'); + if (s) + *s = '\0'; + s = strrchr(test_name, '/'); + if (s) + test_name = s + 1; + + log_severity = LOGS_FATAL; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-d")) { + log_severity = LOGS_DEBUG; + } else if (!strcmp(argv[i], "-s") && i + 1 < argc) { + seed = atoi(argv[++i]); + } else { + fprintf(stderr, "Unknown option\n"); + exit(1); + } + } + + gettimeofday(&tv, NULL); + srandom(seed ? seed : tv.tv_sec ^ (tv.tv_usec << 10)); + + printf("Testing %-30s ", test_name); + fflush(stdout); + + LOG_Initialise(); + LOG_SetMinSeverity(log_severity); + + test_unit(); + + LOG_Finalise(); + + printf("PASS\n"); + + return 0; +} + +double +TST_GetRandomDouble(double min, double max) +{ + return min + random() / 2147483647.0 * (max - min); +} + +void +TST_GetRandomAddress(IPAddr *ip, int family, int bits) +{ + if (family != IPADDR_INET4 && family != IPADDR_INET6) + family = random() % 2 ? IPADDR_INET4 : IPADDR_INET6; + + ip->family = family; + + if (family == IPADDR_INET4) { + if (bits < 0) + bits = 32; + assert(bits <= 32); + + if (bits > 16) + ip->addr.in4 = (uint32_t)random() % (1U << (bits - 16)) << 16 | + (uint32_t)random() % (1U << 16); + else + ip->addr.in4 = (uint32_t)random() % (1U << bits); + } else { + int i, b; + + if (bits < 0) + bits = 128; + assert(bits <= 128); + + for (i = 0, b = 120; i < 16; i++, b -= 8) { + if (b >= bits) { + ip->addr.in6[i] = 0; + } else { + ip->addr.in6[i] = random() % (1U << MIN(bits - b, 8)); + } + } + } +} + +void +TST_SwapAddressBit(IPAddr *ip, unsigned int b) +{ + if (ip->family == IPADDR_INET4) { + assert(b < 32); + ip->addr.in4 ^= 1U << (31 - b); + } else if (ip->family == IPADDR_INET6) { + assert(b < 128); + ip->addr.in6[b / 8] ^= 1U << (7 - b % 8); + } else { + assert(0); + } +} + +static double +read_frequency(void) +{ + return 0.0; +} + +static double +set_frequency(double freq_ppm) +{ + return 0.0; +} + +static void +accrue_offset(double offset, double corr_rate) +{ +} + +static int +apply_step_offset(double offset) +{ + return 0; +} + +static void +offset_convert(struct timespec *raw, double *corr, double *err) +{ + *corr = 0.0; + if (err) + *err = 0.0; +} + +void +TST_RegisterDummyDrivers(void) +{ + lcl_RegisterSystemDrivers(read_frequency, set_frequency, accrue_offset, + apply_step_offset, offset_convert, NULL, NULL); +} diff --git a/test/unit/test.h b/test/unit/test.h new file mode 100644 index 0000000..00d261d --- /dev/null +++ b/test/unit/test.h @@ -0,0 +1,52 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2016 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#ifndef GOT_TEST_H +#define GOT_TEST_H + +#include <addressing.h> + +extern void test_unit(void); + +#define TEST_CHECK(expr) \ + do { \ + if (!(expr)) { \ + TST_Fail(__LINE__); \ + exit(1); \ + } \ + } while (0) + +#define TEST_REQUIRE(expr) \ + do { \ + if (!(expr)) { \ + TST_Skip(__LINE__); \ + exit(0); \ + } \ + } while (0) + +extern void TST_Fail(int line); +extern void TST_Skip(int line); + +extern double TST_GetRandomDouble(double min, double max); +extern void TST_GetRandomAddress(IPAddr *ip, int family, int bits); +extern void TST_SwapAddressBit(IPAddr *ip, unsigned int b); +extern void TST_RegisterDummyDrivers(void); + +#endif diff --git a/test/unit/util.c b/test/unit/util.c new file mode 100644 index 0000000..112676d --- /dev/null +++ b/test/unit/util.c @@ -0,0 +1,744 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2017-2018, 2021 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + */ + +#include <util.c> +#include "test.h" + +static volatile int handled_signal = 0; + +static void +handle_signal(int signal) +{ + handled_signal = signal; +} + +void +test_unit(void) +{ + struct timespec ts, ts2, ts3, ts4; + char buf[16], *s, *s2, *words[3]; + NTP_int64 ntp_ts, ntp_ts2, ntp_fuzz; + NTP_int32 ntp32_ts; + struct timeval tv; + double x, y, nan, inf; + IPAddr ip, ip2, ip3; + IPSockAddr ip_saddr; + Timespec tspec; + Float f; + int i, j, c; + uid_t uid; + gid_t gid; + struct stat st; + FILE *file; + + for (i = -31; i < 31; i++) { + x = pow(2.0, i); + y = UTI_Log2ToDouble(i); + TEST_CHECK(y / x > 0.99999 && y / x < 1.00001); + } + + for (i = -89; i < 63; i++) { + x = pow(2.0, i); + y = UTI_FloatNetworkToHost(UTI_FloatHostToNetwork(x)); + TEST_CHECK(y / x > 0.99999 && y / x < 1.00001); + } + + for (i = 0; i < 100000; i++) { + x = TST_GetRandomDouble(-1000.0, 1000.0); + y = UTI_FloatNetworkToHost(UTI_FloatHostToNetwork(x)); + TEST_CHECK(y / x > 0.99999 && y / x < 1.00001); + + UTI_GetRandomBytes(&f, sizeof (f)); + x = UTI_FloatNetworkToHost(f); + TEST_CHECK(x > 0.0 || x <= 0.0); + } + + TEST_CHECK(UTI_DoubleToNtp32(1.0) == htonl(65536)); + TEST_CHECK(UTI_DoubleToNtp32(0.0) == htonl(0)); + TEST_CHECK(UTI_DoubleToNtp32(1.0 / (65536.0)) == htonl(1)); + TEST_CHECK(UTI_DoubleToNtp32(1.000001 / (65536.0)) == htonl(2)); + TEST_CHECK(UTI_DoubleToNtp32(1.000001) == htonl(65537)); + TEST_CHECK(UTI_DoubleToNtp32(1000000) == htonl(0xffffffff)); + TEST_CHECK(UTI_DoubleToNtp32(-1.0) == htonl(0)); + + UTI_DoubleToTimeval(0.4e-6, &tv); + TEST_CHECK(tv.tv_sec == 0); + TEST_CHECK(tv.tv_usec == 0); + UTI_DoubleToTimeval(-0.4e-6, &tv); + TEST_CHECK(tv.tv_sec == 0); + TEST_CHECK(tv.tv_usec == 0); + UTI_DoubleToTimeval(0.5e-6, &tv); + TEST_CHECK(tv.tv_sec == 0); + TEST_CHECK(tv.tv_usec == 1); + UTI_DoubleToTimeval(-0.5e-6, &tv); + TEST_CHECK(tv.tv_sec == -1); + TEST_CHECK(tv.tv_usec == 999999); + + UTI_DoubleToTimespec(0.9e-9, &ts); + TEST_CHECK(ts.tv_sec == 0); + TEST_CHECK(ts.tv_nsec == 0); + UTI_DoubleToTimespec(1.0e-9, &ts); + TEST_CHECK(ts.tv_sec == 0); + TEST_CHECK(ts.tv_nsec == 1); + UTI_DoubleToTimespec(-0.9e-9, &ts); + TEST_CHECK(ts.tv_sec == 0); + TEST_CHECK(ts.tv_nsec == 0); + UTI_DoubleToTimespec(-1.0e-9, &ts); + TEST_CHECK(ts.tv_sec == -1); + TEST_CHECK(ts.tv_nsec == 999999999); + + ntp_ts.hi = htonl(JAN_1970); + ntp_ts.lo = 0xffffffff; + UTI_Ntp64ToTimespec(&ntp_ts, &ts); +#if defined(HAVE_LONG_TIME_T) && NTP_ERA_SPLIT > 0 + TEST_CHECK(ts.tv_sec == 0x100000000LL * (1 + (NTP_ERA_SPLIT - 1) / 0x100000000LL)); +#else + TEST_CHECK(ts.tv_sec == 0); +#endif + TEST_CHECK(ts.tv_nsec == 999999999); + + ntp_ts.hi = htonl(JAN_1970 - 1); + ntp_ts.lo = htonl(0xffffffff); + ntp_ts2.hi = htonl(JAN_1970 + 1); + ntp_ts2.lo = htonl(0x80000000); + TEST_CHECK(fabs(UTI_DiffNtp64ToDouble(&ntp_ts, &ntp_ts2) + 1.5) < 1e-9); + TEST_CHECK(fabs(UTI_DiffNtp64ToDouble(&ntp_ts2, &ntp_ts) - 1.5) < 1e-9); + + UTI_AddDoubleToTimespec(&ts, 1e-9, &ts); +#if defined(HAVE_LONG_TIME_T) && NTP_ERA_SPLIT > 0 + TEST_CHECK(ts.tv_sec == 1 + 0x100000000LL * (1 + (NTP_ERA_SPLIT - 1) / 0x100000000LL)); +#else + TEST_CHECK(ts.tv_sec == 1); +#endif + TEST_CHECK(ts.tv_nsec == 0); + + ntp_fuzz.hi = 0; + ntp_fuzz.lo = htonl(0xff1234ff); + + UTI_TimespecToNtp64(&ts, &ntp_ts, &ntp_fuzz); + TEST_CHECK(ntp_ts.hi == htonl(JAN_1970 + 1)); + TEST_CHECK(ntp_ts.lo == ntp_fuzz.lo); + + ts.tv_sec = ts.tv_nsec = 1; + UTI_ZeroTimespec(&ts); + TEST_CHECK(ts.tv_sec == 0); + TEST_CHECK(ts.tv_nsec == 0); + TEST_CHECK(UTI_IsZeroTimespec(&ts)); + + ntp_ts.hi = ntp_ts.lo == 1; + UTI_ZeroNtp64(&ntp_ts); + TEST_CHECK(ntp_ts.hi == 0); + TEST_CHECK(ntp_ts.lo == 0); + + tv.tv_sec = tv.tv_usec = 1; + UTI_TimevalToTimespec(&tv, &ts); + TEST_CHECK(ts.tv_sec == 1); + TEST_CHECK(ts.tv_nsec == 1000); + + UTI_TimespecToTimeval(&ts, &tv); + TEST_CHECK(tv.tv_sec == 1); + TEST_CHECK(tv.tv_usec == 1); + + ts.tv_sec = 1; + ts.tv_nsec = 500000000; + TEST_CHECK(fabs(UTI_TimespecToDouble(&ts) - 1.5) < 1.0e-15); + + UTI_DoubleToTimespec(2.75, &ts); + TEST_CHECK(ts.tv_sec == 2); + TEST_CHECK(ts.tv_nsec == 750000000); + + ts.tv_sec = 1; + ts.tv_nsec = 1200000000; + UTI_NormaliseTimespec(&ts); + TEST_CHECK(ts.tv_sec == 2); + TEST_CHECK(ts.tv_nsec == 200000000); + + ts.tv_sec = 1; + ts.tv_nsec = -200000000; + UTI_NormaliseTimespec(&ts); + TEST_CHECK(ts.tv_sec == 0); + TEST_CHECK(ts.tv_nsec == 800000000); + + tv.tv_sec = 1; + tv.tv_usec = 500000; + TEST_CHECK(fabs(UTI_TimevalToDouble(&tv) - 1.5) < 1.0e-15); + + UTI_DoubleToTimeval(2.75, &tv); + TEST_CHECK(tv.tv_sec == 2); + TEST_CHECK(tv.tv_usec == 750000); + + tv.tv_sec = 1; + tv.tv_usec = 1200000; + UTI_NormaliseTimeval(&tv); + TEST_CHECK(tv.tv_sec == 2); + TEST_CHECK(tv.tv_usec == 200000); + + tv.tv_sec = 1; + tv.tv_usec = -200000; + UTI_NormaliseTimeval(&tv); + TEST_CHECK(tv.tv_sec == 0); + TEST_CHECK(tv.tv_usec == 800000); + + UTI_ZeroTimespec(&ts); + UTI_TimespecToNtp64(&ts, &ntp_ts, &ntp_fuzz); + TEST_CHECK(ntp_ts.hi == 0); + TEST_CHECK(ntp_ts.lo == 0); + + TEST_CHECK(UTI_IsZeroNtp64(&ntp_ts)); + + ts.tv_sec = 1; + ntp_ts.hi = htonl(1); + + TEST_CHECK(!UTI_IsZeroTimespec(&ts)); + TEST_CHECK(!UTI_IsZeroNtp64(&ntp_ts)); + + ts.tv_sec = 0; + ntp_ts.hi = 0; + ts.tv_nsec = 1; + ntp_ts.lo = htonl(1); + + TEST_CHECK(!UTI_IsZeroTimespec(&ts)); + TEST_CHECK(!UTI_IsZeroNtp64(&ntp_ts)); + + ntp_ts.hi = 0; + ntp_ts.lo = 0; + + UTI_Ntp64ToTimespec(&ntp_ts, &ts); + TEST_CHECK(UTI_IsZeroTimespec(&ts)); + UTI_TimespecToNtp64(&ts, &ntp_ts, NULL); + TEST_CHECK(UTI_IsZeroNtp64(&ntp_ts)); + + ntp_fuzz.hi = htonl(1); + ntp_fuzz.lo = htonl(3); + ntp_ts.hi = htonl(1); + ntp_ts.lo = htonl(2); + + TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_ts) == 0); + TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_fuzz) < 0); + TEST_CHECK(UTI_CompareNtp64(&ntp_fuzz, &ntp_ts) > 0); + + ntp_ts.hi = htonl(0x80000002); + ntp_ts.lo = htonl(2); + + TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_ts) == 0); + TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_fuzz) < 0); + TEST_CHECK(UTI_CompareNtp64(&ntp_fuzz, &ntp_ts) > 0); + + ntp_fuzz.hi = htonl(0x90000001); + + TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_ts) == 0); + TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_fuzz) < 0); + TEST_CHECK(UTI_CompareNtp64(&ntp_fuzz, &ntp_ts) > 0); + + TEST_CHECK(UTI_IsEqualAnyNtp64(&ntp_ts, &ntp_ts, NULL, NULL)); + TEST_CHECK(UTI_IsEqualAnyNtp64(&ntp_ts, NULL, &ntp_ts, NULL)); + TEST_CHECK(UTI_IsEqualAnyNtp64(&ntp_ts, NULL, NULL, &ntp_ts)); + TEST_CHECK(!UTI_IsEqualAnyNtp64(&ntp_ts, &ntp_fuzz, &ntp_fuzz, &ntp_fuzz)); + + ts.tv_sec = 1; + ts.tv_nsec = 2; + ts2.tv_sec = 1; + ts2.tv_nsec = 3; + + TEST_CHECK(UTI_CompareTimespecs(&ts, &ts) == 0); + TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) < 0); + TEST_CHECK(UTI_CompareTimespecs(&ts2, &ts) > 0); + + ts2.tv_sec = 2; + + TEST_CHECK(UTI_CompareTimespecs(&ts, &ts) == 0); + TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) < 0); + TEST_CHECK(UTI_CompareTimespecs(&ts2, &ts) > 0); + + ts.tv_sec = 2; + ts.tv_nsec = 250000000; + ts2.tv_sec = 1; + ts2.tv_nsec = 750000000; + UTI_DiffTimespecs(&ts3, &ts, &ts2); + TEST_CHECK(ts3.tv_sec == 0); + TEST_CHECK(ts3.tv_nsec == 500000000); + TEST_CHECK(fabs(UTI_DiffTimespecsToDouble(&ts, &ts2) - 0.5) < 1.0e-15); + + ts.tv_sec = 2; + ts.tv_nsec = 250000000; + ts2.tv_sec = 3; + ts2.tv_nsec = 750000000; + UTI_DiffTimespecs(&ts3, &ts, &ts2); + TEST_CHECK(ts3.tv_sec == -2); + TEST_CHECK(ts3.tv_nsec == 500000000); + TEST_CHECK(fabs(UTI_DiffTimespecsToDouble(&ts, &ts2) - -1.5) < 1.0e-15); + + ts.tv_sec = 2; + ts.tv_nsec = 250000000; + UTI_AddDoubleToTimespec(&ts, 2.5, &ts2); + TEST_CHECK(ts2.tv_sec == 4); + TEST_CHECK(ts2.tv_nsec == 750000000); + + ts.tv_sec = 4; + ts.tv_nsec = 500000000; + ts2.tv_sec = 1; + ts2.tv_nsec = 750000000; + UTI_AverageDiffTimespecs(&ts, &ts2, &ts3, &x); + TEST_CHECK(ts3.tv_sec == 3); + TEST_CHECK(ts3.tv_nsec == 125000000); + TEST_CHECK(x == -2.75); + + ts.tv_sec = 4; + ts.tv_nsec = 500000000; + ts2.tv_sec = 1; + ts2.tv_nsec = 750000000; + ts3.tv_sec = 5; + ts3.tv_nsec = 250000000; + UTI_AddDiffToTimespec(&ts, &ts2, &ts3, &ts4); + TEST_CHECK(ts4.tv_sec == 8); + TEST_CHECK(ts4.tv_nsec == 0); + + ts.tv_sec = 1600000000; + ts.tv_nsec = 123; + s = UTI_TimespecToString(&ts); + TEST_CHECK(strcmp(s, "1600000000.000000123") == 0); + + ntp_ts.hi = 1; + ntp_ts.hi = 2; + UTI_Ntp64ToTimespec(&ntp_ts, &ts); + s = UTI_Ntp64ToString(&ntp_ts); + s2 = UTI_TimespecToString(&ts); + TEST_CHECK(strcmp(s, s2) == 0); + + s = UTI_RefidToString(0x41424344); + TEST_CHECK(strcmp(s, "ABCD") == 0); + + ip.family = IPADDR_UNSPEC; + s = UTI_IPToString(&ip); + TEST_CHECK(strcmp(s, "[UNSPEC]") == 0); + TEST_CHECK(UTI_IPToRefid(&ip) == 0); + TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip)); + + ip.family = IPADDR_INET4; + ip.addr.in4 = 0x7f010203; + s = UTI_IPToString(&ip); + TEST_CHECK(strcmp(s, "127.1.2.3") == 0); + TEST_CHECK(UTI_IPToRefid(&ip) == 0x7f010203); + TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip)); + + ip.family = IPADDR_INET6; + memset(&ip.addr.in6, 0, sizeof (ip.addr.in6)); + ip.addr.in6[0] = 0xab; + ip.addr.in6[15] = 0xcd; + s = UTI_IPToString(&ip); +#ifdef FEAT_IPV6 + TEST_CHECK(strcmp(s, "ab00::cd") == 0); +#else + TEST_CHECK(strcmp(s, "ab00:0000:0000:0000:0000:0000:0000:00cd") == 0); +#endif + TEST_CHECK(UTI_IPToRefid(&ip) == 0x5f9aa602); + TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip)); + + ip.family = IPADDR_ID; + ip.addr.id = 12345; + s = UTI_IPToString(&ip); + TEST_CHECK(strcmp(s, "ID#0000012345") == 0); + TEST_CHECK(UTI_IPToRefid(&ip) == 0); + TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip)); + + ip.family = IPADDR_UNSPEC + 10; + s = UTI_IPToString(&ip); + TEST_CHECK(strcmp(s, "[UNKNOWN]") == 0); + TEST_CHECK(UTI_IPToRefid(&ip) == 0); + TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip)); + + TEST_CHECK(UTI_StringToIP("200.4.5.6", &ip)); + TEST_CHECK(ip.family == IPADDR_INET4); + TEST_CHECK(ip.addr.in4 == 0xc8040506); + +#ifdef FEAT_IPV6 + TEST_CHECK(UTI_StringToIP("1234::7890", &ip)); + TEST_CHECK(ip.family == IPADDR_INET6); + TEST_CHECK(ip.addr.in6[0] == 0x12 && ip.addr.in6[1] == 0x34); + TEST_CHECK(ip.addr.in6[2] == 0x00 && ip.addr.in6[13] == 0x00); + TEST_CHECK(ip.addr.in6[14] == 0x78 && ip.addr.in6[15] == 0x90); +#else + TEST_CHECK(!UTI_StringToIP("1234::7890", &ip)); +#endif + + TEST_CHECK(!UTI_StringToIP("ID#0000012345", &ip)); + + TEST_CHECK(UTI_IsStringIP("1.2.3.4")); + TEST_CHECK(!UTI_IsStringIP("127.3.3")); + TEST_CHECK(!UTI_IsStringIP("127.3")); + TEST_CHECK(!UTI_IsStringIP("127")); +#ifdef FEAT_IPV6 + TEST_CHECK(UTI_IsStringIP("1234:5678::aaaa")); +#else + TEST_CHECK(!UTI_IsStringIP("1234:5678::aaaa")); +#endif + TEST_CHECK(!UTI_StringToIP("ID#0000012345", &ip)); + + TEST_CHECK(!UTI_StringToIdIP("1.2.3.4", &ip)); + TEST_CHECK(UTI_StringToIdIP("ID#0000056789", &ip)); + TEST_CHECK(ip.family == IPADDR_ID); + TEST_CHECK(ip.addr.id == 56789); + + for (i = IPADDR_UNSPEC; i <= IPADDR_ID + 1; i++) { + ip.family = i; + TEST_CHECK(UTI_IsIPReal(&ip) == (i == IPADDR_INET4 || i == IPADDR_INET6)); + } + + ip.family = IPADDR_UNSPEC; + UTI_IPHostToNetwork(&ip, &ip2); + TEST_CHECK(ip2.family == htons(IPADDR_UNSPEC)); + UTI_IPNetworkToHost(&ip2, &ip3); + TEST_CHECK(ip3.family == IPADDR_UNSPEC); + + ip.family = IPADDR_INET4; + ip.addr.in4 = 0x12345678; + UTI_IPHostToNetwork(&ip, &ip2); + TEST_CHECK(ip2.family == htons(IPADDR_INET4)); + TEST_CHECK(ip2.addr.in4 == htonl(0x12345678)); + UTI_IPNetworkToHost(&ip2, &ip3); + TEST_CHECK(ip3.family == IPADDR_INET4); + TEST_CHECK(ip3.addr.in4 == 0x12345678); + + ip.family = IPADDR_INET6; + for (i = 0; i < 16; i++) + ip.addr.in6[i] = i; + UTI_IPHostToNetwork(&ip, &ip2); + TEST_CHECK(ip2.family == htons(IPADDR_INET6)); + for (i = 0; i < 16; i++) + TEST_CHECK(ip.addr.in6[i] == i); + UTI_IPNetworkToHost(&ip2, &ip3); + TEST_CHECK(ip3.family == IPADDR_INET6); + for (i = 0; i < 16; i++) + TEST_CHECK(ip.addr.in6[i] == i); + + ip.family = IPADDR_ID; + ip.addr.in4 = 0x87654321; + UTI_IPHostToNetwork(&ip, &ip2); + TEST_CHECK(ip2.family == htons(IPADDR_ID)); + TEST_CHECK(ip2.addr.in4 == htonl(0x87654321)); + UTI_IPNetworkToHost(&ip2, &ip3); + TEST_CHECK(ip3.family == IPADDR_ID); + TEST_CHECK(ip3.addr.in4 == 0x87654321); + + for (i = 0; i < 16; i++) + ip.addr.in6[i] = 0x80; + ip2 = ip; + + for (i = IPADDR_UNSPEC; i <= IPADDR_ID; i++) { + ip.family = i; + ip2.family = i + 1; + TEST_CHECK(UTI_CompareIPs(&ip, &ip2, NULL) < 0); + TEST_CHECK(UTI_CompareIPs(&ip2, &ip, NULL) > 0); + ip2 = ip; + ip2.addr.in4++; + if (i == IPADDR_UNSPEC) { + TEST_CHECK(UTI_CompareIPs(&ip, &ip2, NULL) == 0); + TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip) == 0); + } else { + TEST_CHECK(UTI_CompareIPs(&ip, &ip2, NULL) < 0); + TEST_CHECK(UTI_CompareIPs(&ip2, &ip, NULL) > 0); + if (i == IPADDR_ID) { + TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip) < 0); + TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip2) < 0); + } else { + TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip) == 0); + TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip2) < 0); + } + } + } + + ip_saddr.ip_addr.family = IPADDR_INET4; + ip_saddr.ip_addr.addr.in4 = 0x01020304; + ip_saddr.port = 12345; + s = UTI_IPSockAddrToString(&ip_saddr); + TEST_CHECK(strcmp(s, "1.2.3.4:12345") == 0); + + s = UTI_TimeToLogForm(2000000000); + TEST_CHECK(strcmp(s, "2033-05-18 03:33:20") == 0); + + ts.tv_sec = 3; + ts.tv_nsec = 500000000; + ts2.tv_sec = 4; + ts2.tv_nsec = 250000000; + UTI_AdjustTimespec(&ts, &ts2, &ts3, &x, 2.0, -5.0); + TEST_CHECK(fabs(x - 6.5) < 1.0e-15); + TEST_CHECK((ts3.tv_sec == 10 && ts3.tv_nsec == 0) || + (ts3.tv_sec == 9 && ts3.tv_nsec == 999999999)); + + for (i = -32; i <= 32; i++) { + for (j = c = 0; j < 1000; j++) { + UTI_GetNtp64Fuzz(&ntp_fuzz, i); + if (i <= 0) + TEST_CHECK(ntp_fuzz.hi == 0); + if (i < 0) + TEST_CHECK(ntohl(ntp_fuzz.lo) < 1U << (32 + i)); + else if (i < 32) + TEST_CHECK(ntohl(ntp_fuzz.hi) < 1U << i); + if (ntohl(ntp_fuzz.lo) >= 1U << (31 + CLAMP(-31, i, 0))) + c++; + } + + if (i == -32) + TEST_CHECK(c == 0); + else + TEST_CHECK(c > 400 && c < 600); + } + + TEST_CHECK(UTI_DoubleToNtp32(-1.0) == htonl(0)); + TEST_CHECK(UTI_DoubleToNtp32(0.0) == htonl(0)); + TEST_CHECK(UTI_DoubleToNtp32(1e-9) == htonl(1)); + TEST_CHECK(UTI_DoubleToNtp32(32768.0) == htonl(0x80000000)); + TEST_CHECK(UTI_DoubleToNtp32(65536.0) == htonl(0xffffffff)); + TEST_CHECK(UTI_DoubleToNtp32(65537.0) == htonl(0xffffffff)); + + TEST_CHECK(UTI_DoubleToNtp32f28(-1.0) == htonl(0)); + TEST_CHECK(UTI_DoubleToNtp32f28(0.0) == htonl(0)); + TEST_CHECK(UTI_DoubleToNtp32f28(1e-9) == htonl(1)); + TEST_CHECK(UTI_DoubleToNtp32f28(4e-9) == htonl(2)); + TEST_CHECK(UTI_DoubleToNtp32f28(8.0) == htonl(0x80000000)); + TEST_CHECK(UTI_DoubleToNtp32f28(16.0) == htonl(0xffffffff)); + TEST_CHECK(UTI_DoubleToNtp32f28(16.1) == htonl(0xffffffff)); + TEST_CHECK(UTI_DoubleToNtp32f28(16.1) == htonl(0xffffffff)); + + TEST_CHECK(UTI_Ntp32f28ToDouble(htonl(0xffffffff)) >= 65535.999); + for (i = 0; i < 100000; i++) { + UTI_GetRandomBytes(&ntp32_ts, sizeof (ntp32_ts)); + TEST_CHECK(UTI_DoubleToNtp32(UTI_Ntp32ToDouble(ntp32_ts)) == ntp32_ts); + TEST_CHECK(UTI_DoubleToNtp32f28(UTI_Ntp32f28ToDouble(ntp32_ts)) == ntp32_ts); + } + + ts.tv_nsec = 0; + + ts.tv_sec = 10; + TEST_CHECK(!UTI_IsTimeOffsetSane(&ts, -20.0)); + +#ifdef HAVE_LONG_TIME_T + ts.tv_sec = NTP_ERA_SPLIT + (1LL << 32); +#else + ts.tv_sec = 0x7fffffff - MIN_ENDOFTIME_DISTANCE; +#endif + TEST_CHECK(!UTI_IsTimeOffsetSane(&ts, 10.0)); + TEST_CHECK(UTI_IsTimeOffsetSane(&ts, -20.0)); + + TEST_CHECK(UTI_Log2ToDouble(-1) == 0.5); + TEST_CHECK(UTI_Log2ToDouble(0) == 1.0); + TEST_CHECK(UTI_Log2ToDouble(1) == 2.0); + TEST_CHECK(UTI_Log2ToDouble(-31) < UTI_Log2ToDouble(-30)); + TEST_CHECK(UTI_Log2ToDouble(-32) == UTI_Log2ToDouble(-31)); + TEST_CHECK(UTI_Log2ToDouble(30) < UTI_Log2ToDouble(32)); + TEST_CHECK(UTI_Log2ToDouble(31) == UTI_Log2ToDouble(32)); + + UTI_TimespecHostToNetwork(&ts, &tspec); +#ifdef HAVE_LONG_TIME_T + TEST_CHECK(tspec.tv_sec_high == htonl(ts.tv_sec >> 32)); +#else + TEST_CHECK(tspec.tv_sec_high == htonl(TV_NOHIGHSEC)); +#endif + TEST_CHECK(tspec.tv_sec_low == htonl(ts.tv_sec)); + TEST_CHECK(tspec.tv_nsec == htonl(ts.tv_nsec)); + UTI_TimespecNetworkToHost(&tspec, &ts2); + TEST_CHECK(!UTI_CompareTimespecs(&ts, &ts2)); + + TEST_CHECK(UTI_CmacNameToAlgorithm("AES128") == CMC_AES128); + TEST_CHECK(UTI_CmacNameToAlgorithm("AES256") == CMC_AES256); + TEST_CHECK(UTI_CmacNameToAlgorithm("NOSUCHCMAC") == CMC_INVALID); + + TEST_CHECK(UTI_HashNameToAlgorithm("MD5") == HSH_MD5); + TEST_CHECK(UTI_HashNameToAlgorithm("SHA1") == HSH_SHA1); + TEST_CHECK(UTI_HashNameToAlgorithm("SHA256") == HSH_SHA256); + TEST_CHECK(UTI_HashNameToAlgorithm("SHA384") == HSH_SHA384); + TEST_CHECK(UTI_HashNameToAlgorithm("SHA512") == HSH_SHA512); + TEST_CHECK(UTI_HashNameToAlgorithm("SHA3-224") == HSH_SHA3_224); + TEST_CHECK(UTI_HashNameToAlgorithm("SHA3-256") == HSH_SHA3_256); + TEST_CHECK(UTI_HashNameToAlgorithm("SHA3-384") == HSH_SHA3_384); + TEST_CHECK(UTI_HashNameToAlgorithm("SHA3-512") == HSH_SHA3_512); + TEST_CHECK(UTI_HashNameToAlgorithm("TIGER") == HSH_TIGER); + TEST_CHECK(UTI_HashNameToAlgorithm("WHIRLPOOL") == HSH_WHIRLPOOL); + TEST_CHECK(UTI_HashNameToAlgorithm("NOSUCHHASH") == HSH_INVALID); + + i = open("/dev/null", 0); + TEST_CHECK(UTI_FdSetCloexec(i)); + j = fcntl(i, F_GETFD); + TEST_CHECK(j & F_GETFD); + close(i); + + UTI_SetQuitSignalsHandler(handle_signal, 0); + TEST_CHECK(handled_signal == 0); + kill(getpid(), SIGPIPE); + while (handled_signal == 0) + ; + TEST_CHECK(handled_signal == SIGPIPE); + + s = UTI_PathToDir("/aaa/bbb/ccc/ddd"); + TEST_CHECK(!strcmp(s, "/aaa/bbb/ccc")); + Free(s); + s = UTI_PathToDir("aaa"); + TEST_CHECK(!strcmp(s, ".")); + Free(s); + s = UTI_PathToDir("/aaaa"); + TEST_CHECK(!strcmp(s, "/")); + Free(s); + + nan = strtod("nan", NULL); + inf = strtod("inf", NULL); + + TEST_CHECK(MIN(2.0, -1.0) == -1.0); + TEST_CHECK(MIN(-1.0, 2.0) == -1.0); + TEST_CHECK(MIN(inf, 2.0) == 2.0); + + TEST_CHECK(MAX(2.0, -1.0) == 2.0); + TEST_CHECK(MAX(-1.0, 2.0) == 2.0); + TEST_CHECK(MAX(inf, 2.0) == inf); + + TEST_CHECK(CLAMP(1.0, -1.0, 2.0) == 1.0); + TEST_CHECK(CLAMP(1.0, 3.0, 2.0) == 2.0); + TEST_CHECK(CLAMP(1.0, inf, 2.0) == 2.0); + TEST_CHECK(CLAMP(1.0, nan, 2.0) == 2.0); + + TEST_CHECK(SQUARE(3.0) == 3.0 * 3.0); + + rmdir("testdir"); + + uid = geteuid(); + gid = getegid(); + + TEST_CHECK(UTI_CreateDirAndParents("testdir", 0700, uid, gid)); + + TEST_CHECK(UTI_CheckDirPermissions("testdir", 0700, uid, gid)); + TEST_CHECK(!UTI_CheckDirPermissions("testdir", 0300, uid, gid)); + TEST_CHECK(!UTI_CheckDirPermissions("testdir", 0700, uid + 1, gid)); + TEST_CHECK(!UTI_CheckDirPermissions("testdir", 0700, uid, gid + 1)); + + umask(0); + + unlink("testfile"); + file = UTI_OpenFile(NULL, "testfile", NULL, 'r', 0); + TEST_CHECK(!file); + TEST_CHECK(stat("testfile", &st) < 0); + + file = UTI_OpenFile(NULL, "testfile", NULL, 'w', 0644); + TEST_CHECK(file); + TEST_CHECK(stat("testfile", &st) == 0); + TEST_CHECK((st.st_mode & 0777) == 0644); + fclose(file); + + file = UTI_OpenFile(".", "test", "file", 'W', 0640); + TEST_CHECK(file); + TEST_CHECK(stat("testfile", &st) == 0); + TEST_CHECK((st.st_mode & 0777) == 0640); + fclose(file); + + file = UTI_OpenFile(NULL, "test", "file", 'r', 0); + TEST_CHECK(file); + fclose(file); + + TEST_CHECK(UTI_RenameTempFile(NULL, "testfil", "e", NULL)); + TEST_CHECK(stat("testfil", &st) == 0); + file = UTI_OpenFile(NULL, "testfil", NULL, 'R', 0); + TEST_CHECK(file); + fclose(file); + + TEST_CHECK(UTI_RenameTempFile(NULL, "test", "fil", "file")); + TEST_CHECK(stat("testfile", &st) == 0); + file = UTI_OpenFile(NULL, "testfile", NULL, 'R', 0); + TEST_CHECK(file); + fclose(file); + + TEST_CHECK(UTI_RemoveFile(NULL, "testfile", NULL)); + TEST_CHECK(stat("testfile", &st) < 0); + TEST_CHECK(!UTI_RemoveFile(NULL, "testfile", NULL)); + + for (i = c = 0; i < 100000; i++) { + j = random() % (sizeof (buf) + 1); + UTI_GetRandomBytesUrandom(buf, j); + if (j && buf[j - 1] % 2) + c++; + if (random() % 10000 == 0) { + UTI_ResetGetRandomFunctions(); + TEST_CHECK(!urandom_file); + } + } + TEST_CHECK(c > 46000 && c < 48000); + + for (i = c = 0; i < 100000; i++) { + j = random() % (sizeof (buf) + 1); + UTI_GetRandomBytes(buf, j); + if (j && buf[j - 1] % 2) + c++; + if (random() % 10000 == 0) { + UTI_ResetGetRandomFunctions(); +#if HAVE_GETRANDOM + TEST_CHECK(getrandom_buf_available == 0); +#endif + } + } + TEST_CHECK(c > 46000 && c < 48000); + + assert(sizeof (buf) >= 16); + TEST_CHECK(UTI_HexToBytes("", buf, sizeof (buf)) == 0); + TEST_CHECK(UTI_HexToBytes("0", buf, sizeof (buf)) == 0); + TEST_CHECK(UTI_HexToBytes("00123456789ABCDEF", buf, sizeof (buf)) == 0); + TEST_CHECK(UTI_HexToBytes("00123456789ABCDEF0", buf, 8) == 0); + TEST_CHECK(UTI_HexToBytes("00123456789ABCDEF0", buf, sizeof (buf)) == 9); + TEST_CHECK(memcmp(buf, "\x00\x12\x34\x56\x78\x9A\xBC\xDE\xF0", 9) == 0); + memcpy(buf, "AB123456780001", 15); + TEST_CHECK(UTI_HexToBytes(buf, buf, sizeof (buf)) == 7); + TEST_CHECK(memcmp(buf, "\xAB\x12\x34\x56\x78\x00\x01", 7) == 0); + + TEST_CHECK(UTI_BytesToHex("", 0, buf, 0) == 0); + TEST_CHECK(UTI_BytesToHex("\xAB\x12\x34\x56\x78\x00\x01", 7, buf, 14) == 0); + TEST_CHECK(UTI_BytesToHex("\xAB\x12\x34\x56\x78\x00\x01", 7, buf, 15) == 1); + TEST_CHECK(strcmp(buf, "AB123456780001") == 0); + TEST_CHECK(UTI_BytesToHex("\xAB\x12\x34\x56\x78\x00\x01", 0, buf, 15) == 1); + TEST_CHECK(strcmp(buf, "") == 0); + + TEST_CHECK(snprintf(buf, sizeof (buf), "%s", "") < sizeof (buf)); + TEST_CHECK(UTI_SplitString(buf, words, 3) == 0); + TEST_CHECK(!words[0]); + TEST_CHECK(snprintf(buf, sizeof (buf), "%s", " ") < sizeof (buf)); + TEST_CHECK(UTI_SplitString(buf, words, 3) == 0); + TEST_CHECK(!words[0]); + TEST_CHECK(snprintf(buf, sizeof (buf), "%s", "a \n ") < sizeof (buf)); + TEST_CHECK(UTI_SplitString(buf, words, 3) == 1); + TEST_CHECK(words[0] == buf + 0); + TEST_CHECK(strcmp(words[0], "a") == 0); + TEST_CHECK(snprintf(buf, sizeof (buf), "%s", " a ") < sizeof (buf)); + TEST_CHECK(UTI_SplitString(buf, words, 3) == 1); + TEST_CHECK(words[0] == buf + 2); + TEST_CHECK(strcmp(words[0], "a") == 0); + TEST_CHECK(snprintf(buf, sizeof (buf), "%s", " \n a") < sizeof (buf)); + TEST_CHECK(UTI_SplitString(buf, words, 3) == 1); + TEST_CHECK(words[0] == buf + 4); + TEST_CHECK(strcmp(words[0], "a") == 0); + TEST_CHECK(snprintf(buf, sizeof (buf), "%s", "a b") < sizeof (buf)); + TEST_CHECK(UTI_SplitString(buf, words, 1) == 2); + TEST_CHECK(snprintf(buf, sizeof (buf), "%s", "a b") < sizeof (buf)); + TEST_CHECK(UTI_SplitString(buf, words, 2) == 2); + TEST_CHECK(words[0] == buf + 0); + TEST_CHECK(words[1] == buf + 4); + TEST_CHECK(strcmp(words[0], "a") == 0); + TEST_CHECK(strcmp(words[1], "b") == 0); + TEST_CHECK(snprintf(buf, sizeof (buf), "%s", " a b ") < sizeof (buf)); + TEST_CHECK(UTI_SplitString(buf, words, 3) == 2); + TEST_CHECK(words[0] == buf + 1); + TEST_CHECK(words[1] == buf + 3); + TEST_CHECK(strcmp(words[0], "a") == 0); + TEST_CHECK(strcmp(words[1], "b") == 0); +} |