summaryrefslogtreecommitdiffstats
path: root/test/unit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 18:43:21 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 18:43:21 +0000
commit104f986b0650b8f93540785d2bcf486905e49b62 (patch)
tree2b2ae5113d9b57425d4bb3f726e325316b87e00a /test/unit
parentInitial commit. (diff)
downloadchrony-upstream/3.4.tar.xz
chrony-upstream/3.4.zip
Adding upstream version 3.4.upstream/3.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/Makefile.in47
-rw-r--r--test/unit/addrfilt.c83
-rw-r--r--test/unit/clientlog.c84
-rw-r--r--test/unit/hash.c123
-rw-r--r--test/unit/hwclock.c84
-rw-r--r--test/unit/keys.c147
-rw-r--r--test/unit/ntp_core.c477
-rw-r--r--test/unit/ntp_core.keys6
-rw-r--r--test/unit/ntp_sources.c100
-rw-r--r--test/unit/regress.c119
-rw-r--r--test/unit/samplefilt.c117
-rw-r--r--test/unit/smooth.c63
-rw-r--r--test/unit/sources.c142
-rw-r--r--test/unit/test.c179
-rw-r--r--test/unit/test.h46
-rw-r--r--test/unit/util.c267
16 files changed, 2084 insertions, 0 deletions
diff --git a/test/unit/Makefile.in b/test/unit/Makefile.in
new file mode 100644
index 0000000..e789a1b
--- /dev/null
+++ b/test/unit/Makefile.in
@@ -0,0 +1,47 @@
+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))))
+
+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..a412b69
--- /dev/null
+++ b/test/unit/clientlog.c
@@ -0,0 +1,84 @@
+/*
+ **********************************************************************
+ * 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 <clientlog.c>
+#include "test.h"
+
+void
+test_unit(void)
+{
+ int i, j, index;
+ struct timespec ts;
+ IPAddr ip;
+ char conf[][100] = {
+ "clientloglimit 10000",
+ "ratelimit interval 3 burst 4 leak 3",
+ "cmdratelimit interval 3 burst 4 leak 3",
+ };
+
+ CNF_Initialise(0, 0);
+ for (i = 0; i < sizeof conf / sizeof conf[0]; i++)
+ CNF_ParseLine(NULL, i + 1, conf[i]);
+
+ 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));
+
+ if (random() % 2) {
+ index = CLG_LogNTPAccess(&ip, &ts);
+ TEST_CHECK(index >= 0);
+ CLG_LimitNTPResponseRate(index);
+ } else {
+ index = CLG_LogCommandAccess(&ip, &ts);
+ TEST_CHECK(index >= 0);
+ CLG_LimitCommandResponseRate(index);
+ }
+
+ UTI_AddDoubleToTimespec(&ts, (1 << random() % 14) / 100.0, &ts);
+ }
+ }
+
+ DEBUG_LOG("records %u", ARR_GetSize(records));
+ TEST_CHECK(ARR_GetSize(records) == 64);
+
+ for (i = j = 0; i < 10000; i++) {
+ ts.tv_sec += 1;
+ index = CLG_LogNTPAccess(&ip, &ts);
+ TEST_CHECK(index >= 0);
+ if (!CLG_LimitNTPResponseRate(index))
+ j++;
+ }
+
+ DEBUG_LOG("requests %d responses %d", i, j);
+ TEST_CHECK(j * 4 < i && j * 6 > i);
+
+ CLG_Finalise();
+ CNF_Finalise();
+}
diff --git a/test/unit/hash.c b/test/unit/hash.c
new file mode 100644
index 0000000..5cde039
--- /dev/null
+++ b/test/unit/hash.c
@@ -0,0 +1,123 @@
+/*
+ **********************************************************************
+ * 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 "test.h"
+
+struct hash_test {
+ const char *name;
+ const unsigned char out[MAX_HASH_LENGTH];
+ unsigned 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", "\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 },
+ { "RMD128", "\x6f\xd7\x1f\x37\x47\x0f\xbd\x42\x57\xc8\xbb\xee\xba\x65\xf9\x35", 16 },
+ { "RMD160", "\x7a\x88\xec\xc7\x09\xc5\x65\x34\x11\x24\xe3\xf9\xf7\xa5\xbf\xc6"
+ "\x01\xe2\xc9\x32", 20},
+ { "RMD256", "\x59\xdf\xd4\xcb\xc9\xbe\x7c\x27\x08\xa7\x23\xf7\xb3\x0c\xf0\x0d"
+ "\xa0\xcf\x5b\x18\x16\x51\x56\x6d\xda\x7b\x87\x24\x9d\x83\x35\xe1", 32 },
+ { "RMD320", "\x68\x98\x10\xf4\xb6\x79\xb6\x15\xf1\x48\x2d\x73\xd0\x23\x84\x01"
+ "\xbf\xaa\x67\xcf\x1e\x35\x5c\xbf\xe9\xb8\xaf\xe1\xee\x0d\xf0\x6b"
+ "\xe2\x3a\x9a\x3a\xa7\x56\xad\x70", 40},
+ { "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 }
+ };
+
+ unsigned int length;
+ int i, j, hash_id;
+
+ for (i = 0; tests[i].name[0] != '\0'; i++) {
+ hash_id = HSH_GetHashId(tests[i].name);
+ if (hash_id < 0) {
+ TEST_CHECK(strcmp(tests[i].name, "MD5"));
+#ifdef FEAT_SECHASH
+ TEST_CHECK(strcmp(tests[i].name, "SHA1"));
+ TEST_CHECK(strcmp(tests[i].name, "SHA256"));
+ TEST_CHECK(strcmp(tests[i].name, "SHA384"));
+ TEST_CHECK(strcmp(tests[i].name, "SHA512"));
+#endif
+ continue;
+ }
+
+ DEBUG_LOG("testing %s", tests[i].name);
+
+ for (j = 0; j <= sizeof (out); j++) {
+ TEST_CHECK(HSH_GetHashId(tests[i].name) == hash_id);
+ TEST_CHECK(HSH_GetHashId("nosuchhash") < 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..6462c5c
--- /dev/null
+++ b/test/unit/hwclock.c
@@ -0,0 +1,84 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016-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 <hwclock.c>
+#include "test.h"
+
+void
+test_unit(void)
+{
+ struct timespec start_hw_ts, start_local_ts, hw_ts, local_ts, ts;
+ HCL_Instance clock;
+ double freq, jitter, interval, dj, sum;
+ int i, j, k, count;
+
+ LCL_Initialise();
+
+ for (i = 1; i <= 8; i++) {
+ clock = HCL_CreateInstance(random() % (1 << i), 1 << i, 1.0);
+
+ 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;
+
+ 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)) {
+ 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))
+ HCL_AccumulateSample(clock, &hw_ts, &local_ts, 2.0 * jitter);
+
+ 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();
+}
diff --git a/test/unit/keys.c b/test/unit/keys.c
new file mode 100644
index 0000000..ac995fa
--- /dev/null
+++ b/test/unit/keys.c
@@ -0,0 +1,147 @@
+/*
+ **********************************************************************
+ * 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 <keys.c>
+#include "test.h"
+
+#define KEYS 100
+#define KEYFILE "keys.test-keys"
+
+static
+uint32_t write_random_key(FILE *f)
+{
+ const char *hash_name;
+ char key[128];
+ uint32_t id;
+ int i, length;
+
+ length = random() % sizeof (key) + 1;
+ length = MAX(length, 4);
+ UTI_GetRandomBytes(&id, sizeof (id));
+ UTI_GetRandomBytes(key, length);
+
+ switch (random() % 6) {
+#ifdef FEAT_SECHASH
+ case 0:
+ hash_name = "SHA1";
+ break;
+ case 1:
+ hash_name = "SHA256";
+ break;
+ case 2:
+ hash_name = "SHA384";
+ break;
+ case 3:
+ hash_name = "SHA512";
+ break;
+#endif
+ case 4:
+ hash_name = "MD5";
+ break;
+ default:
+ hash_name = "";
+ }
+
+ fprintf(f, "%u %s %s", id, hash_name, random() % 2 ? "HEX:" : "");
+ 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;
+ 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_GetAuthDelay(keys[j]) >= 0);
+ 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));
+ }
+
+ for (j = 0; j < 1000; j++) {
+ UTI_GetRandomBytes(&key, sizeof (key));
+ if (KEY_KeyKnown(key))
+ continue;
+ 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();
+}
diff --git a/test/unit/ntp_core.c b/test/unit/ntp_core.c
new file mode 100644
index 0000000..5e519e5
--- /dev/null
+++ b/test/unit/ntp_core.c
@@ -0,0 +1,477 @@
+/*
+ **********************************************************************
+ * 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_io.h>
+#include <sched.h>
+#include <local.h>
+#include "test.h"
+
+static struct timespec current_time;
+static NTP_Receive_Buffer 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_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() % 6 + 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.ntp_pkt, 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.ntp_pkt, 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.ntp_pkt, res_length);
+ }
+}
+
+static void
+send_response(int interleaved, int authenticated, int allow_update, int valid_ts, int valid_auth)
+{
+ NTP_Packet *req, *res;
+ int auth_len = 0;
+
+ req = &req_buffer.ntp_pkt;
+ res = &res_buffer.ntp_pkt;
+
+ TEST_CHECK(req_length >= NTP_NORMAL_PACKET_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);
+ }
+ }
+
+ if (authenticated) {
+ res->auth_keyid = req->auth_keyid ? req->auth_keyid : htonl(get_random_key_id());
+ auth_len = KEY_GetAuthLength(ntohl(res->auth_keyid));
+ assert(auth_len);
+ if (NTP_LVM_TO_VERSION(res->lvm) == 4 && random() % 2)
+ auth_len = MIN(auth_len, NTP_MAX_V4_MAC_LENGTH - 4);
+
+ if (KEY_GenerateAuth(ntohl(res->auth_keyid), (unsigned char *)res,
+ NTP_NORMAL_PACKET_LENGTH, res->auth_data, auth_len) != auth_len)
+ assert(0);
+ res_length = NTP_NORMAL_PACKET_LENGTH + 4 + auth_len;
+ } else {
+ res_length = NTP_NORMAL_PACKET_LENGTH;
+ }
+
+ if (!valid_auth && authenticated) {
+ assert(auth_len);
+
+ switch (random() % 4) {
+ case 0:
+ res->auth_keyid = htonl(ntohl(res->auth_keyid) + 1);
+ break;
+ case 1:
+ res->auth_keyid = htonl(ntohl(res->auth_keyid) ^ 1);
+ if (KEY_GenerateAuth(ntohl(res->auth_keyid), (unsigned char *)res,
+ NTP_NORMAL_PACKET_LENGTH, res->auth_data, auth_len) != auth_len)
+ assert(0);
+ break;
+ case 2:
+ res->auth_data[random() % auth_len]++;
+ break;
+ case 3:
+ res_length = NTP_NORMAL_PACKET_LENGTH + 4 * (random() % ((4 + auth_len) / 4));
+ if (NTP_LVM_TO_VERSION(res->lvm) == 4 &&
+ res_length == NTP_NORMAL_PACKET_LENGTH + NTP_MAX_V4_MAC_LENGTH)
+ res_length -= 4;
+ break;
+ default:
+ assert(0);
+ }
+ }
+}
+
+static void
+process_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 prev_open_socket, ret;
+
+ res = &res_buffer.ntp_pkt;
+
+ 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;
+ prev_open_socket = inst->local_addr.sock_fd != INVALID_SOCK_FD;
+
+ ret = NCR_ProcessRxKnown(inst, &local_addr, &local_ts, res, res_length);
+
+ if (good > 0)
+ TEST_CHECK(ret);
+ else if (!good)
+ TEST_CHECK(!ret);
+
+ if (prev_open_socket)
+ TEST_CHECK(prev_rx_count + 1 == inst->report.total_rx_count);
+ else
+ TEST_CHECK(prev_rx_count == 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_Receive_Buffer *packet_queue,
+ int queue_length, int updated_init)
+{
+ do {
+ res_buffer = packet_queue[random() % queue_length];
+ } while (!UTI_CompareNtp64(&res_buffer.ntp_pkt.transmit_ts,
+ &inst->remote_ntp_tx));
+ process_response(inst, 0, 0, 0, updated_init);
+ advance_time(1e-6);
+}
+
+#define PACKET_QUEUE_LENGTH 10
+
+void
+test_unit(void)
+{
+ char source_line[] = "127.0.0.1 maxdelaydevratio 1e6";
+ 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_Receive_Buffer packet_queue[PACKET_QUEUE_LENGTH];
+
+ 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(IPADDR_UNSPEC);
+ NCR_Initialise();
+ REF_Initialise();
+
+ TST_SuspendLogging();
+ KEY_Initialise();
+ TST_ResumeLogging();
+
+ CNF_SetupAccessRestrictions();
+
+ CPS_ParseNTPSourceAdd(source_line, &source);
+
+ for (i = 0; i < 1000; i++) {
+ if (random() % 2)
+ source.params.interleaved = 1;
+ if (random() % 2)
+ source.params.authkey = get_random_key_id();
+ source.params.version = random() % 4 + 1;
+
+ UTI_ZeroTimespec(&current_time);
+ advance_time(TST_GetRandomDouble(1.0, 1e9));
+
+ TST_GetRandomAddress(&remote_addr.ip_addr, IPADDR_UNSPEC, -1);
+ remote_addr.port = 123;
+
+ inst1 = NCR_GetInstance(&remote_addr, random() % 2 ? NTP_SERVER : NTP_PEER, &source.params);
+ 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 || authenticated);
+ updated = (valid || inst1->mode == MODE_ACTIVE) &&
+ (!source.params.authkey || authenticated);
+ has_updated = has_updated || updated;
+ if (inst1->mode == MODE_CLIENT)
+ updated = 0;
+
+ send_request(inst1);
+
+ send_response(interleaved, authenticated, 1, 0, 1);
+ DEBUG_LOG("response 1");
+ process_response(inst1, 0, 0, 0, updated);
+
+ if (source.params.authkey) {
+ send_response(interleaved, authenticated, 1, 1, 0);
+ DEBUG_LOG("response 2");
+ process_response(inst1, 0, 0, 0, 0);
+ }
+
+ send_response(interleaved, authenticated, 1, 1, 1);
+ DEBUG_LOG("response 3");
+ process_response(inst1, -1, valid, valid, updated);
+ DEBUG_LOG("response 4");
+ process_response(inst1, 0, 0, 0, 0);
+
+ advance_time(-1.0);
+
+ send_response(interleaved, authenticated, 1, 1, 1);
+ DEBUG_LOG("response 5");
+ process_response(inst1, 0, 0, 0, updated && valid);
+
+ advance_time(1.0);
+
+ send_response(interleaved, authenticated, 1, 1, 1);
+ DEBUG_LOG("response 6");
+ process_response(inst1, 0, 0, valid && updated, updated);
+ }
+
+ NCR_DestroyInstance(inst1);
+
+ inst1 = NCR_GetInstance(&remote_addr, random() % 2 ? NTP_SERVER : NTP_PEER, &source.params);
+ 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);
+ process_response(inst1, 1, 1, 1, 0);
+ advance_time(1 << inst1->local_poll);
+ }
+
+ NCR_DestroyInstance(inst1);
+
+ inst1 = NCR_GetInstance(&remote_addr, NTP_PEER, &source.params);
+ NCR_StartInstance(inst1);
+ inst2 = NCR_GetInstance(&remote_addr, NTP_PEER, &source.params);
+ 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");
+ process_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");
+ process_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);
+ }
+
+ KEY_Finalise();
+ REF_Finalise();
+ NCR_Finalise();
+ NIO_Finalise();
+ SRC_Finalise();
+ SCH_Finalise();
+ LCL_Finalise();
+ CNF_Finalise();
+ HSH_Finalise();
+}
diff --git a/test/unit/ntp_core.keys b/test/unit/ntp_core.keys
new file mode 100644
index 0000000..f06237f
--- /dev/null
+++ b/test/unit/ntp_core.keys
@@ -0,0 +1,6 @@
+2 MD5 HEX:38979C567358C0896F4D9D459A3C8B8478654579
+3 MD5 HEX:38979C567358C0896F4D9D459A3C8B8478654579
+4 SHA1 HEX:B71744EA01FBF01CA30D173ECDDF901952AE356A
+5 SHA1 HEX:B71744EA01FBF01CA30D173ECDDF901952AE356A
+6 SHA512 HEX:DE027482F22B201FC20863F58C74095E7906089F
+7 SHA512 HEX:DE027482F22B201FC20863F58C74095E7906089F
diff --git a/test/unit/ntp_sources.c b/test/unit/ntp_sources.c
new file mode 100644
index 0000000..ea8f19c
--- /dev/null
+++ b/test/unit/ntp_sources.c
@@ -0,0 +1,100 @@
+/*
+ **********************************************************************
+ * 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 <ntp_sources.c>
+#include <conf.h>
+#include <ntp_io.h>
+#include "test.h"
+
+void
+test_unit(void)
+{
+ int i, j, k, slot, found;
+ uint32_t hash = 0;
+ NTP_Remote_Address addrs[256], addr;
+ SourceParameters params;
+ char conf[] = "port 0";
+
+ memset(&params, 0, sizeof (params));
+
+ CNF_Initialise(0, 0);
+ CNF_ParseLine(NULL, 1, conf);
+
+ LCL_Initialise();
+ SCH_Initialise();
+ SRC_Initialise();
+ NIO_Initialise(IPADDR_UNSPEC);
+ NCR_Initialise();
+ NSR_Initialise();
+
+ 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++) {
+ do {
+ TST_GetRandomAddress(&addrs[j].ip_addr, IPADDR_UNSPEC, -1);
+ } while (UTI_IPToHash(&addrs[j].ip_addr) % (1U << i) != hash % (1U << i));
+
+ 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));
+
+ NSR_AddSource(&addrs[j], random() % 2 ? NTP_SERVER : NTP_PEER, &params);
+
+ for (k = 0; k < j; k++) {
+ addr = addrs[k];
+ find_slot(&addr, &slot, &found);
+ TEST_CHECK(found == 2);
+ TEST_CHECK(!UTI_CompareIPs(&get_record(slot)->remote_addr->ip_addr,
+ &addr.ip_addr, NULL));
+ addr.port++;
+ find_slot(&addr, &slot, &found);
+ TEST_CHECK(found == 1);
+ TEST_CHECK(!UTI_CompareIPs(&get_record(slot)->remote_addr->ip_addr,
+ &addr.ip_addr, NULL));
+ }
+ }
+
+ 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]);
+
+ for (k = 0; k < sizeof (addrs) / sizeof (addrs[0]); k++) {
+ find_slot(&addrs[k], &slot, &found);
+ TEST_CHECK(found == (k <= j ? 0 : 2));
+ }
+ }
+ }
+
+ NSR_Finalise();
+ NCR_Finalise();
+ NIO_Finalise();
+ SRC_Finalise();
+ SCH_Finalise();
+ LCL_Finalise();
+ CNF_Finalise();
+ HSH_Finalise();
+}
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..a371b3a
--- /dev/null
+++ b/test/unit/samplefilt.c
@@ -0,0 +1,117 @@
+/*
+ **********************************************************************
+ * 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();
+
+ 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);
+
+ 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);
+ sample_in.stratum = random() % 16;
+ sample_in.leap = random() % 4;
+
+ 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_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);
+ TEST_CHECK(sample_out.leap >= 0 && sample_out.leap <= 3);
+ TEST_CHECK(sample_out.stratum >= 0 && sample_out.stratum <= 15);
+
+ if (max_samples == 1)
+ TEST_CHECK(!memcmp(&sample_in, &sample_out, sizeof (sample_in)));
+
+ } else {
+ SPF_DropSamples(filter);
+ }
+
+ 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/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..83f7060
--- /dev/null
+++ b/test/unit/sources.c
@@ -0,0 +1,142 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016, 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 <sources.c>
+#include "test.h"
+
+void
+test_unit(void)
+{
+ SRC_Instance srcs[16];
+ RPT_SourceReport report;
+ NTP_Sample sample;
+ IPAddr addr;
+ int i, j, k, l, samples, sel_options;
+
+ CNF_Initialise(0, 0);
+ LCL_Initialise();
+ TST_RegisterDummyDrivers();
+ SCH_Initialise();
+ SRC_Initialise();
+ REF_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);
+
+ TST_GetRandomAddress(&addr, IPADDR_UNSPEC, -1);
+
+ 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] = SRC_CreateNewInstance(UTI_IPToRefid(&addr), SRC_NTP, sel_options, &addr,
+ SRC_DEFAULT_MINSAMPLES, SRC_DEFAULT_MAXSAMPLES,
+ 0.0, 1.0);
+ 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;
+ sample.stratum = 1;
+ sample.leap = LEAP_Normal;
+
+ 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 %u", k, 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]->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, passed,
+ trusted_passed, trusted, required_passed, required);
+
+ TEST_CHECK(!trusted || !passed || (passed_lo >= trusted_lo && passed_hi <= trusted_hi));
+ TEST_CHECK(!passed || trusted != 1 || (trusted == 1 && trusted_passed == 1));
+ TEST_CHECK(!passed || !required || required_passed > 0);
+ }
+ }
+
+ for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) {
+ SRC_ReportSource(j, &report, &sample.time);
+ SRC_DestroyInstance(srcs[j]);
+ }
+ }
+
+ 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..3a9ec74
--- /dev/null
+++ b/test/unit/test.c
@@ -0,0 +1,179 @@
+/*
+ **********************************************************************
+ * 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 "test.h"
+
+void
+TST_Fail(int line)
+{
+ printf("FAIL (on line %d)\n", line);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ 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;
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-d")) {
+ LOG_SetDebugLevel(2);
+ } 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();
+
+ test_unit();
+
+ LOG_Finalise();
+
+ printf("PASS\n");
+
+ return 0;
+}
+
+void TST_SuspendLogging(void)
+{
+ LOG_OpenFileLog("/dev/null");
+}
+
+void TST_ResumeLogging(void)
+{
+ LOG_OpenFileLog(NULL);
+}
+
+double
+TST_GetRandomDouble(double min, double max)
+{
+ return min + (double)random() / RAND_MAX * (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..f409252
--- /dev/null
+++ b/test/unit/test.h
@@ -0,0 +1,46 @@
+/*
+ **********************************************************************
+ * 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)
+
+extern void TST_Fail(int line);
+
+extern void TST_SuspendLogging(void);
+extern void TST_ResumeLogging(void);
+
+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..6ce6f90
--- /dev/null
+++ b/test/unit/util.c
@@ -0,0 +1,267 @@
+#include <util.c>
+#include "test.h"
+
+void test_unit(void) {
+ NTP_int64 ntp_ts, ntp_fuzz;
+ NTP_int32 ntp32_ts;
+ struct timespec ts, ts2;
+ struct timeval tv;
+ struct sockaddr_un sun;
+ double x, y, nan, inf;
+ Timespec tspec;
+ Float f;
+ int i, j, c;
+ char buf[16], *s;
+ uid_t uid;
+ gid_t gid;
+
+ 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);
+ }
+
+ 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_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);
+ TEST_CHECK(ts.tv_sec == 0);
+ TEST_CHECK(ts.tv_nsec == 999999999);
+
+ UTI_AddDoubleToTimespec(&ts, 1e-9, &ts);
+ TEST_CHECK(ts.tv_sec == 1);
+ 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 = 0;
+ UTI_TimespecToNtp64(&ts, &ntp_ts, &ntp_fuzz);
+ TEST_CHECK(ntp_ts.hi == 0);
+ TEST_CHECK(ntp_ts.lo == 0);
+
+ TEST_CHECK(UTI_IsZeroTimespec(&ts));
+ 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);
+
+ 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);
+ }
+
+ 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));
+
+ UTI_TimespecHostToNetwork(&ts, &tspec);
+ UTI_TimespecNetworkToHost(&tspec, &ts2);
+ TEST_CHECK(!UTI_CompareTimespecs(&ts, &ts2));
+
+ for (i = c = 0; i < 100000; i++) {
+ j = random() % (sizeof (buf) + 1);
+ UTI_GetRandomBytes(buf, j);
+ if (j && buf[j - 1] % 2)
+ c++;
+ }
+ TEST_CHECK(c > 46000 && c < 48000);
+
+ for (i = 1; i < 2 * BUFFER_LENGTH; i++) {
+ sun.sun_family = AF_UNIX;
+ for (j = 0; j + 1 < i && j + 1 < sizeof (sun.sun_path); j++)
+ sun.sun_path[j] = 'A' + j % 26;
+ sun.sun_path[j] = '\0';
+ s = UTI_SockaddrToString((struct sockaddr *)&sun);
+ if (i <= BUFFER_LENGTH) {
+ TEST_CHECK(!strcmp(s, sun.sun_path));
+ } else {
+ TEST_CHECK(!strncmp(s, sun.sun_path, BUFFER_LENGTH - 2));
+ TEST_CHECK(s[BUFFER_LENGTH - 2] == '>');
+ }
+ }
+
+ 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));
+
+ TST_SuspendLogging();
+ 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));
+ TST_ResumeLogging();
+}