summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:09:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:09:41 +0000
commit3271d1ac389d2ec93db9c5b9ce0991ce478476cf (patch)
tree35ff7d180e1ccc061f28535d7435b5ba1789e734 /test
parentInitial commit. (diff)
downloadchrony-upstream/4.3.tar.xz
chrony-upstream/4.3.zip
Adding upstream version 4.3.upstream/4.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test')
-rwxr-xr-xtest/compilation/001-features36
-rwxr-xr-xtest/compilation/002-scanbuild16
-rwxr-xr-xtest/compilation/003-sanitizers103
-rw-r--r--test/kernel/Makefile7
-rw-r--r--test/kernel/adjtime.c185
-rw-r--r--test/kernel/ntpadjtime.c75
-rwxr-xr-xtest/simulation/001-defaults13
-rwxr-xr-xtest/simulation/002-largenetwork22
-rwxr-xr-xtest/simulation/003-largefreqoffset19
-rwxr-xr-xtest/simulation/004-largetimeoffset18
-rwxr-xr-xtest/simulation/005-externalstep46
-rwxr-xr-xtest/simulation/006-largejitter21
-rwxr-xr-xtest/simulation/007-largewander20
-rwxr-xr-xtest/simulation/008-ntpera50
-rwxr-xr-xtest/simulation/009-sourceselection40
-rwxr-xr-xtest/simulation/010-multrecv17
-rwxr-xr-xtest/simulation/011-asymjitter18
-rwxr-xr-xtest/simulation/012-daemonts15
-rwxr-xr-xtest/simulation/013-nameserv15
-rwxr-xr-xtest/simulation/101-poll56
-rwxr-xr-xtest/simulation/102-iburst23
-rwxr-xr-xtest/simulation/103-initstepslew63
-rwxr-xr-xtest/simulation/104-driftfile23
-rwxr-xr-xtest/simulation/105-ntpauth96
-rwxr-xr-xtest/simulation/106-refclock143
-rwxr-xr-xtest/simulation/107-allowdeny48
-rwxr-xr-xtest/simulation/108-peer54
-rwxr-xr-xtest/simulation/109-makestep41
-rwxr-xr-xtest/simulation/110-chronyc496
-rwxr-xr-xtest/simulation/111-knownclient17
-rwxr-xr-xtest/simulation/112-port57
-rwxr-xr-xtest/simulation/113-leapsecond61
-rwxr-xr-xtest/simulation/114-presend25
-rwxr-xr-xtest/simulation/115-cmdmontime24
-rwxr-xr-xtest/simulation/116-minsources24
-rwxr-xr-xtest/simulation/117-fallbackdrift24
-rwxr-xr-xtest/simulation/118-maxdelay42
-rwxr-xr-xtest/simulation/119-smoothtime82
-rwxr-xr-xtest/simulation/120-selectoptions89
-rwxr-xr-xtest/simulation/121-orphan26
-rwxr-xr-xtest/simulation/122-xleave91
-rwxr-xr-xtest/simulation/123-mindelay27
-rwxr-xr-xtest/simulation/124-tai45
-rwxr-xr-xtest/simulation/125-packetloss29
-rwxr-xr-xtest/simulation/126-burst45
-rwxr-xr-xtest/simulation/127-filter43
-rwxr-xr-xtest/simulation/128-nocontrol27
-rwxr-xr-xtest/simulation/129-reload109
-rwxr-xr-xtest/simulation/130-quit31
-rwxr-xr-xtest/simulation/131-maxchange20
-rwxr-xr-xtest/simulation/132-logchange21
-rwxr-xr-xtest/simulation/133-hwtimestamp60
-rwxr-xr-xtest/simulation/134-log35
-rwxr-xr-xtest/simulation/135-ratelimit18
-rwxr-xr-xtest/simulation/136-broadcast16
-rwxr-xr-xtest/simulation/137-pool49
-rwxr-xr-xtest/simulation/138-syncloop34
-rwxr-xr-xtest/simulation/139-nts312
-rwxr-xr-xtest/simulation/140-noclientlog21
-rwxr-xr-xtest/simulation/141-copy19
-rwxr-xr-xtest/simulation/142-ptpport41
-rwxr-xr-xtest/simulation/143-manual70
-rwxr-xr-xtest/simulation/144-exp155
-rwxr-xr-xtest/simulation/201-freqaccumulation35
-rwxr-xr-xtest/simulation/202-prefer21
-rw-r--r--test/simulation/README11
-rwxr-xr-xtest/simulation/run90
-rw-r--r--test/simulation/test.common528
-rwxr-xr-xtest/system/001-minimal13
-rwxr-xr-xtest/system/002-extended13
-rwxr-xr-xtest/system/003-memlock15
-rwxr-xr-xtest/system/004-priority15
-rwxr-xr-xtest/system/006-privdrop17
-rwxr-xr-xtest/system/007-cmdmon181
-rwxr-xr-xtest/system/008-confload74
-rwxr-xr-xtest/system/009-binddevice24
-rwxr-xr-xtest/system/010-nts66
-rwxr-xr-xtest/system/099-scfilter24
-rwxr-xr-xtest/system/100-clockupdate30
-rwxr-xr-xtest/system/101-rtc19
-rwxr-xr-xtest/system/102-hwtimestamp28
-rwxr-xr-xtest/system/103-refclock19
-rwxr-xr-xtest/system/104-systemdirs19
-rwxr-xr-xtest/system/199-scfilter24
-rwxr-xr-xtest/system/run64
-rw-r--r--test/system/test.common373
-rw-r--r--test/unit/Makefile.in48
-rw-r--r--test/unit/addrfilt.c83
-rw-r--r--test/unit/clientlog.c292
-rw-r--r--test/unit/cmac.c109
-rw-r--r--test/unit/hash.c131
-rw-r--r--test/unit/hwclock.c117
-rw-r--r--test/unit/keys.c173
-rw-r--r--test/unit/ntp_auth.c289
-rw-r--r--test/unit/ntp_core.c623
-rw-r--r--test/unit/ntp_core.keys8
-rw-r--r--test/unit/ntp_ext.c167
-rw-r--r--test/unit/ntp_sources.c364
-rw-r--r--test/unit/nts_ke.crt8
-rw-r--r--test/unit/nts_ke.key25
-rw-r--r--test/unit/nts_ke_client.c144
-rw-r--r--test/unit/nts_ke_server.c230
-rw-r--r--test/unit/nts_ke_session.c224
-rw-r--r--test/unit/nts_ntp_auth.c112
-rw-r--r--test/unit/nts_ntp_client.c284
-rw-r--r--test/unit/nts_ntp_server.c176
-rw-r--r--test/unit/quantiles.c68
-rw-r--r--test/unit/regress.c119
-rw-r--r--test/unit/samplefilt.c120
-rw-r--r--test/unit/siv.c321
-rw-r--r--test/unit/smooth.c63
-rw-r--r--test/unit/sources.c289
-rw-r--r--test/unit/test.c181
-rw-r--r--test/unit/test.h52
-rw-r--r--test/unit/util.c744
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(&current_time, x, &current_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(&current_time, &res->receive_ts, NULL);
+ advance_time(TST_GetRandomDouble(-1e-4, 1e-3));
+ UTI_TimespecToNtp64(&current_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(&current_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);
+}