summaryrefslogtreecommitdiffstats
path: root/src/lib/util
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/util
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/lib/util/Makefile.am111
-rw-r--r--src/lib/util/Makefile.in1161
-rw-r--r--src/lib/util/bigints.h26
-rw-r--r--src/lib/util/boost_time_utils.cc50
-rw-r--r--src/lib/util/boost_time_utils.h56
-rw-r--r--src/lib/util/buffer.h611
-rw-r--r--src/lib/util/chrono_time_utils.cc102
-rw-r--r--src/lib/util/chrono_time_utils.h48
-rw-r--r--src/lib/util/csv_file.cc557
-rw-r--r--src/lib/util/csv_file.h575
-rw-r--r--src/lib/util/dhcp_space.cc25
-rw-r--r--src/lib/util/dhcp_space.h45
-rw-r--r--src/lib/util/doubles.h29
-rw-r--r--src/lib/util/encode/base16_from_binary.h103
-rw-r--r--src/lib/util/encode/base32hex.h56
-rw-r--r--src/lib/util/encode/base32hex_from_binary.h105
-rw-r--r--src/lib/util/encode/base64.h71
-rw-r--r--src/lib/util/encode/base_n.cc494
-rw-r--r--src/lib/util/encode/binary_from_base16.h112
-rw-r--r--src/lib/util/encode/binary_from_base32hex.h115
-rw-r--r--src/lib/util/encode/hex.h66
-rw-r--r--src/lib/util/encode/utf8.cc35
-rw-r--r--src/lib/util/encode/utf8.h27
-rw-r--r--src/lib/util/file_utilities.cc69
-rw-r--r--src/lib/util/file_utilities.h34
-rw-r--r--src/lib/util/filename.cc148
-rw-r--r--src/lib/util/filename.h164
-rw-r--r--src/lib/util/hash.h57
-rw-r--r--src/lib/util/io/Makefile.am14
-rw-r--r--src/lib/util/io/Makefile.in914
-rw-r--r--src/lib/util/io/fd.cc78
-rw-r--r--src/lib/util/io/fd.h53
-rw-r--r--src/lib/util/io/fd_share.cc166
-rw-r--r--src/lib/util/io/fd_share.h61
-rw-r--r--src/lib/util/io/pktinfo_utilities.h43
-rw-r--r--src/lib/util/io/sockaddr_util.h76
-rw-r--r--src/lib/util/io/socketsession.cc438
-rw-r--r--src/lib/util/io/socketsession.h495
-rw-r--r--src/lib/util/io_utilities.h185
-rw-r--r--src/lib/util/labeled_value.cc117
-rw-r--r--src/lib/util/labeled_value.h176
-rw-r--r--src/lib/util/memory_segment.h333
-rw-r--r--src/lib/util/memory_segment_local.cc71
-rw-r--r--src/lib/util/memory_segment_local.h100
-rw-r--r--src/lib/util/multi_threading_mgr.cc291
-rw-r--r--src/lib/util/multi_threading_mgr.h374
-rw-r--r--src/lib/util/optional.h201
-rw-r--r--src/lib/util/pid_file.cc93
-rw-r--r--src/lib/util/pid_file.h97
-rw-r--r--src/lib/util/pointer_util.h49
-rw-r--r--src/lib/util/python/Makefile.am3
-rw-r--r--src/lib/util/python/Makefile.in555
-rw-r--r--src/lib/util/python/const2hdr.py56
-rw-r--r--src/lib/util/python/gen_wiredata.py.in1448
-rw-r--r--src/lib/util/range_utilities.h61
-rw-r--r--src/lib/util/readwrite_mutex.h187
-rw-r--r--src/lib/util/reconnect_ctl.cc41
-rw-r--r--src/lib/util/reconnect_ctl.h143
-rw-r--r--src/lib/util/staged_value.h118
-rw-r--r--src/lib/util/state_model.cc465
-rw-r--r--src/lib/util/state_model.h850
-rw-r--r--src/lib/util/stopwatch.cc85
-rw-r--r--src/lib/util/stopwatch.h129
-rw-r--r--src/lib/util/stopwatch_impl.cc103
-rw-r--r--src/lib/util/stopwatch_impl.h122
-rw-r--r--src/lib/util/strutil.cc467
-rw-r--r--src/lib/util/strutil.h403
-rw-r--r--src/lib/util/tests/Makefile.am74
-rw-r--r--src/lib/util/tests/Makefile.in1730
-rw-r--r--src/lib/util/tests/base32hex_unittest.cc158
-rw-r--r--src/lib/util/tests/base64_unittest.cc93
-rw-r--r--src/lib/util/tests/bigint_unittest.cc94
-rw-r--r--src/lib/util/tests/boost_time_utils_unittest.cc99
-rw-r--r--src/lib/util/tests/buffer_unittest.cc341
-rw-r--r--src/lib/util/tests/chrono_time_utils_unittest.cc159
-rw-r--r--src/lib/util/tests/csv_file_unittest.cc700
-rw-r--r--src/lib/util/tests/dhcp_space_unittest.cc32
-rw-r--r--src/lib/util/tests/doubles_unittest.cc32
-rw-r--r--src/lib/util/tests/fd_share_tests.cc72
-rw-r--r--src/lib/util/tests/fd_tests.cc71
-rw-r--r--src/lib/util/tests/file_utilities_unittest.cc86
-rw-r--r--src/lib/util/tests/filename_unittest.cc225
-rw-r--r--src/lib/util/tests/hash_unittest.cc34
-rw-r--r--src/lib/util/tests/hex_unittest.cc114
-rw-r--r--src/lib/util/tests/io_utilities_unittest.cc160
-rw-r--r--src/lib/util/tests/labeled_value_unittest.cc101
-rw-r--r--src/lib/util/tests/memory_segment_common_unittest.cc100
-rw-r--r--src/lib/util/tests/memory_segment_common_unittest.h28
-rw-r--r--src/lib/util/tests/memory_segment_local_unittest.cc117
-rw-r--r--src/lib/util/tests/multi_threading_mgr_unittest.cc636
-rw-r--r--src/lib/util/tests/optional_unittest.cc162
-rw-r--r--src/lib/util/tests/pid_file_unittest.cc206
-rw-r--r--src/lib/util/tests/range_utilities_unittest.cc50
-rw-r--r--src/lib/util/tests/readwrite_mutex_unittest.cc470
-rw-r--r--src/lib/util/tests/run_unittests.cc18
-rw-r--r--src/lib/util/tests/staged_value_unittest.cc106
-rw-r--r--src/lib/util/tests/state_model_unittest.cc916
-rw-r--r--src/lib/util/tests/stopwatch_unittest.cc307
-rw-r--r--src/lib/util/tests/strutil_unittest.cc642
-rw-r--r--src/lib/util/tests/thread_pool_unittest.cc661
-rw-r--r--src/lib/util/tests/time_utilities_unittest.cc155
-rw-r--r--src/lib/util/tests/triplet_unittest.cc125
-rw-r--r--src/lib/util/tests/unlock_guard_unittests.cc236
-rw-r--r--src/lib/util/tests/utf8_unittest.cc50
-rw-r--r--src/lib/util/tests/versioned_csv_file_unittest.cc501
-rw-r--r--src/lib/util/tests/watch_socket_unittests.cc263
-rw-r--r--src/lib/util/tests/watched_thread_unittest.cc218
-rw-r--r--src/lib/util/thread_pool.h527
-rw-r--r--src/lib/util/time_utilities.cc203
-rw-r--r--src/lib/util/time_utilities.h165
-rw-r--r--src/lib/util/triplet.h127
-rw-r--r--src/lib/util/unittests/Makefile.am32
-rw-r--r--src/lib/util/unittests/Makefile.in846
-rw-r--r--src/lib/util/unittests/README5
-rw-r--r--src/lib/util/unittests/check_valgrind.cc33
-rw-r--r--src/lib/util/unittests/check_valgrind.h45
-rw-r--r--src/lib/util/unittests/fork.cc141
-rw-r--r--src/lib/util/unittests/fork.h44
-rw-r--r--src/lib/util/unittests/interprocess_util.cc42
-rw-r--r--src/lib/util/unittests/interprocess_util.h23
-rw-r--r--src/lib/util/unittests/mock_socketsession.h151
-rw-r--r--src/lib/util/unittests/newhook.cc45
-rw-r--r--src/lib/util/unittests/newhook.h74
-rw-r--r--src/lib/util/unittests/resource.cc29
-rw-r--r--src/lib/util/unittests/resource.h31
-rw-r--r--src/lib/util/unittests/run_all.cc89
-rw-r--r--src/lib/util/unittests/run_all.h44
-rw-r--r--src/lib/util/unittests/testdata.cc55
-rw-r--r--src/lib/util/unittests/testdata.h46
-rw-r--r--src/lib/util/unittests/textdata.h95
-rw-r--r--src/lib/util/unittests/wiredata.cc46
-rw-r--r--src/lib/util/unittests/wiredata.h37
-rw-r--r--src/lib/util/unlock_guard.h47
-rw-r--r--src/lib/util/util.dox93
-rw-r--r--src/lib/util/versioned_csv_file.cc247
-rw-r--r--src/lib/util/versioned_csv_file.h317
-rw-r--r--src/lib/util/watch_socket.cc165
-rw-r--r--src/lib/util/watch_socket.h143
-rw-r--r--src/lib/util/watched_thread.cc103
-rw-r--r--src/lib/util/watched_thread.h145
140 files changed, 29290 insertions, 0 deletions
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
new file mode 100644
index 0000000..e2833a9
--- /dev/null
+++ b/src/lib/util/Makefile.am
@@ -0,0 +1,111 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
+SUBDIRS = . io unittests tests python
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-util.la
+libkea_util_la_SOURCES =
+libkea_util_la_SOURCES += bigints.h
+libkea_util_la_SOURCES += boost_time_utils.h boost_time_utils.cc
+libkea_util_la_SOURCES += buffer.h io_utilities.h
+libkea_util_la_SOURCES += chrono_time_utils.h chrono_time_utils.cc
+libkea_util_la_SOURCES += csv_file.h csv_file.cc
+libkea_util_la_SOURCES += dhcp_space.h dhcp_space.cc
+libkea_util_la_SOURCES += doubles.h
+libkea_util_la_SOURCES += file_utilities.h file_utilities.cc
+libkea_util_la_SOURCES += filename.h filename.cc
+libkea_util_la_SOURCES += hash.h
+libkea_util_la_SOURCES += labeled_value.h labeled_value.cc
+libkea_util_la_SOURCES += memory_segment.h
+libkea_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
+libkea_util_la_SOURCES += multi_threading_mgr.h multi_threading_mgr.cc
+libkea_util_la_SOURCES += optional.h
+libkea_util_la_SOURCES += pid_file.h pid_file.cc
+libkea_util_la_SOURCES += pointer_util.h
+libkea_util_la_SOURCES += range_utilities.h
+libkea_util_la_SOURCES += readwrite_mutex.h
+libkea_util_la_SOURCES += reconnect_ctl.h reconnect_ctl.cc
+libkea_util_la_SOURCES += staged_value.h
+libkea_util_la_SOURCES += state_model.cc state_model.h
+libkea_util_la_SOURCES += stopwatch.cc stopwatch.h
+libkea_util_la_SOURCES += stopwatch_impl.cc stopwatch_impl.h
+libkea_util_la_SOURCES += strutil.h strutil.cc
+libkea_util_la_SOURCES += thread_pool.h
+libkea_util_la_SOURCES += time_utilities.h time_utilities.cc
+libkea_util_la_SOURCES += triplet.h
+libkea_util_la_SOURCES += unlock_guard.h
+libkea_util_la_SOURCES += versioned_csv_file.h versioned_csv_file.cc
+libkea_util_la_SOURCES += watch_socket.cc watch_socket.h
+libkea_util_la_SOURCES += watched_thread.cc watched_thread.h
+libkea_util_la_SOURCES += encode/base16_from_binary.h
+libkea_util_la_SOURCES += encode/base32hex.h encode/base64.h
+libkea_util_la_SOURCES += encode/base32hex_from_binary.h
+libkea_util_la_SOURCES += encode/base_n.cc encode/hex.h
+libkea_util_la_SOURCES += encode/binary_from_base32hex.h
+libkea_util_la_SOURCES += encode/binary_from_base16.h
+libkea_util_la_SOURCES += encode/utf8.cc encode/utf8.h
+
+libkea_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+
+libkea_util_la_LDFLAGS = -no-undefined -version-info 68:0:0
+
+EXTRA_DIST = util.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+libkea_util_includedir = $(pkgincludedir)/util
+libkea_util_include_HEADERS = \
+ bigints.h \
+ boost_time_utils.h \
+ buffer.h \
+ csv_file.h \
+ dhcp_space.h \
+ doubles.h \
+ file_utilities.h \
+ filename.h \
+ hash.h \
+ io_utilities.h \
+ labeled_value.h \
+ memory_segment.h \
+ memory_segment_local.h \
+ multi_threading_mgr.h \
+ optional.h \
+ pid_file.h \
+ pointer_util.h \
+ range_utilities.h \
+ readwrite_mutex.h \
+ reconnect_ctl.h \
+ staged_value.h \
+ state_model.h \
+ stopwatch.h \
+ stopwatch_impl.h \
+ strutil.h \
+ thread_pool.h \
+ time_utilities.h \
+ triplet.h \
+ unlock_guard.h \
+ versioned_csv_file.h \
+ watch_socket.h \
+ watched_thread.h
+
+libkea_util_encode_includedir = $(pkgincludedir)/util/encode
+libkea_util_encode_include_HEADERS = \
+ encode/base16_from_binary.h \
+ encode/base32hex.h \
+ encode/base32hex_from_binary.h \
+ encode/base64.h \
+ encode/binary_from_base16.h \
+ encode/binary_from_base32hex.h \
+ encode/hex.h \
+ encode/utf8.h
+
+libkea_util_io_includedir = $(pkgincludedir)/util/io
+libkea_util_io_include_HEADERS = \
+ io/fd.h \
+ io/fd_share.h \
+ io/pktinfo_utilities.h \
+ io/sockaddr_util.h
diff --git a/src/lib/util/Makefile.in b/src/lib/util/Makefile.in
new file mode 100644
index 0000000..f79840f
--- /dev/null
+++ b/src/lib/util/Makefile.in
@@ -0,0 +1,1161 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/util
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am \
+ $(libkea_util_encode_include_HEADERS) \
+ $(libkea_util_include_HEADERS) \
+ $(libkea_util_io_include_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" \
+ "$(DESTDIR)$(libkea_util_encode_includedir)" \
+ "$(DESTDIR)$(libkea_util_includedir)" \
+ "$(DESTDIR)$(libkea_util_io_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libkea_util_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+am__dirstamp = $(am__leading_dot)dirstamp
+am_libkea_util_la_OBJECTS = boost_time_utils.lo chrono_time_utils.lo \
+ csv_file.lo dhcp_space.lo file_utilities.lo filename.lo \
+ labeled_value.lo memory_segment_local.lo \
+ multi_threading_mgr.lo pid_file.lo reconnect_ctl.lo \
+ state_model.lo stopwatch.lo stopwatch_impl.lo strutil.lo \
+ time_utilities.lo versioned_csv_file.lo watch_socket.lo \
+ watched_thread.lo encode/base_n.lo encode/utf8.lo
+libkea_util_la_OBJECTS = $(am_libkea_util_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libkea_util_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_util_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/boost_time_utils.Plo \
+ ./$(DEPDIR)/chrono_time_utils.Plo ./$(DEPDIR)/csv_file.Plo \
+ ./$(DEPDIR)/dhcp_space.Plo ./$(DEPDIR)/file_utilities.Plo \
+ ./$(DEPDIR)/filename.Plo ./$(DEPDIR)/labeled_value.Plo \
+ ./$(DEPDIR)/memory_segment_local.Plo \
+ ./$(DEPDIR)/multi_threading_mgr.Plo ./$(DEPDIR)/pid_file.Plo \
+ ./$(DEPDIR)/reconnect_ctl.Plo ./$(DEPDIR)/state_model.Plo \
+ ./$(DEPDIR)/stopwatch.Plo ./$(DEPDIR)/stopwatch_impl.Plo \
+ ./$(DEPDIR)/strutil.Plo ./$(DEPDIR)/time_utilities.Plo \
+ ./$(DEPDIR)/versioned_csv_file.Plo \
+ ./$(DEPDIR)/watch_socket.Plo ./$(DEPDIR)/watched_thread.Plo \
+ encode/$(DEPDIR)/base_n.Plo encode/$(DEPDIR)/utf8.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libkea_util_la_SOURCES)
+DIST_SOURCES = $(libkea_util_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libkea_util_encode_include_HEADERS) \
+ $(libkea_util_include_HEADERS) \
+ $(libkea_util_io_include_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AUTOMAKE_OPTIONS = subdir-objects
+SUBDIRS = . io unittests tests python
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+lib_LTLIBRARIES = libkea-util.la
+libkea_util_la_SOURCES = bigints.h boost_time_utils.h \
+ boost_time_utils.cc buffer.h io_utilities.h \
+ chrono_time_utils.h chrono_time_utils.cc csv_file.h \
+ csv_file.cc dhcp_space.h dhcp_space.cc doubles.h \
+ file_utilities.h file_utilities.cc filename.h filename.cc \
+ hash.h labeled_value.h labeled_value.cc memory_segment.h \
+ memory_segment_local.h memory_segment_local.cc \
+ multi_threading_mgr.h multi_threading_mgr.cc optional.h \
+ pid_file.h pid_file.cc pointer_util.h range_utilities.h \
+ readwrite_mutex.h reconnect_ctl.h reconnect_ctl.cc \
+ staged_value.h state_model.cc state_model.h stopwatch.cc \
+ stopwatch.h stopwatch_impl.cc stopwatch_impl.h strutil.h \
+ strutil.cc thread_pool.h time_utilities.h time_utilities.cc \
+ triplet.h unlock_guard.h versioned_csv_file.h \
+ versioned_csv_file.cc watch_socket.cc watch_socket.h \
+ watched_thread.cc watched_thread.h encode/base16_from_binary.h \
+ encode/base32hex.h encode/base64.h \
+ encode/base32hex_from_binary.h encode/base_n.cc encode/hex.h \
+ encode/binary_from_base32hex.h encode/binary_from_base16.h \
+ encode/utf8.cc encode/utf8.h
+libkea_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_util_la_LDFLAGS = -no-undefined -version-info 68:0:0
+EXTRA_DIST = util.dox
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+libkea_util_includedir = $(pkgincludedir)/util
+libkea_util_include_HEADERS = \
+ bigints.h \
+ boost_time_utils.h \
+ buffer.h \
+ csv_file.h \
+ dhcp_space.h \
+ doubles.h \
+ file_utilities.h \
+ filename.h \
+ hash.h \
+ io_utilities.h \
+ labeled_value.h \
+ memory_segment.h \
+ memory_segment_local.h \
+ multi_threading_mgr.h \
+ optional.h \
+ pid_file.h \
+ pointer_util.h \
+ range_utilities.h \
+ readwrite_mutex.h \
+ reconnect_ctl.h \
+ staged_value.h \
+ state_model.h \
+ stopwatch.h \
+ stopwatch_impl.h \
+ strutil.h \
+ thread_pool.h \
+ time_utilities.h \
+ triplet.h \
+ unlock_guard.h \
+ versioned_csv_file.h \
+ watch_socket.h \
+ watched_thread.h
+
+libkea_util_encode_includedir = $(pkgincludedir)/util/encode
+libkea_util_encode_include_HEADERS = \
+ encode/base16_from_binary.h \
+ encode/base32hex.h \
+ encode/base32hex_from_binary.h \
+ encode/base64.h \
+ encode/binary_from_base16.h \
+ encode/binary_from_base32hex.h \
+ encode/hex.h \
+ encode/utf8.h
+
+libkea_util_io_includedir = $(pkgincludedir)/util/io
+libkea_util_io_include_HEADERS = \
+ io/fd.h \
+ io/fd_share.h \
+ io/pktinfo_utilities.h \
+ io/sockaddr_util.h
+
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/util/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+encode/$(am__dirstamp):
+ @$(MKDIR_P) encode
+ @: > encode/$(am__dirstamp)
+encode/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) encode/$(DEPDIR)
+ @: > encode/$(DEPDIR)/$(am__dirstamp)
+encode/base_n.lo: encode/$(am__dirstamp) \
+ encode/$(DEPDIR)/$(am__dirstamp)
+encode/utf8.lo: encode/$(am__dirstamp) \
+ encode/$(DEPDIR)/$(am__dirstamp)
+
+libkea-util.la: $(libkea_util_la_OBJECTS) $(libkea_util_la_DEPENDENCIES) $(EXTRA_libkea_util_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_util_la_LINK) -rpath $(libdir) $(libkea_util_la_OBJECTS) $(libkea_util_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f encode/*.$(OBJEXT)
+ -rm -f encode/*.lo
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boost_time_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chrono_time_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/csv_file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp_space.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_utilities.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filename.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/labeled_value.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/memory_segment_local.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/multi_threading_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pid_file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reconnect_ctl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/state_model.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stopwatch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stopwatch_impl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strutil.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/time_utilities.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/versioned_csv_file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/watch_socket.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/watched_thread.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@encode/$(DEPDIR)/base_n.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@encode/$(DEPDIR)/utf8.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+ -rm -rf encode/.libs encode/_libs
+install-libkea_util_encode_includeHEADERS: $(libkea_util_encode_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_util_encode_include_HEADERS)'; test -n "$(libkea_util_encode_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_util_encode_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_util_encode_includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libkea_util_encode_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_util_encode_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_util_encode_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_util_encode_include_HEADERS)'; test -n "$(libkea_util_encode_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_util_encode_includedir)'; $(am__uninstall_files_from_dir)
+install-libkea_util_includeHEADERS: $(libkea_util_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_util_include_HEADERS)'; test -n "$(libkea_util_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_util_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_util_includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libkea_util_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_util_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_util_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_util_include_HEADERS)'; test -n "$(libkea_util_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_util_includedir)'; $(am__uninstall_files_from_dir)
+install-libkea_util_io_includeHEADERS: $(libkea_util_io_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_util_io_include_HEADERS)'; test -n "$(libkea_util_io_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_util_io_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_util_io_includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libkea_util_io_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_util_io_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_util_io_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_util_io_include_HEADERS)'; test -n "$(libkea_util_io_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_util_io_includedir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_util_encode_includedir)" "$(DESTDIR)$(libkea_util_includedir)" "$(DESTDIR)$(libkea_util_io_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -rm -f encode/$(DEPDIR)/$(am__dirstamp)
+ -rm -f encode/$(am__dirstamp)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/boost_time_utils.Plo
+ -rm -f ./$(DEPDIR)/chrono_time_utils.Plo
+ -rm -f ./$(DEPDIR)/csv_file.Plo
+ -rm -f ./$(DEPDIR)/dhcp_space.Plo
+ -rm -f ./$(DEPDIR)/file_utilities.Plo
+ -rm -f ./$(DEPDIR)/filename.Plo
+ -rm -f ./$(DEPDIR)/labeled_value.Plo
+ -rm -f ./$(DEPDIR)/memory_segment_local.Plo
+ -rm -f ./$(DEPDIR)/multi_threading_mgr.Plo
+ -rm -f ./$(DEPDIR)/pid_file.Plo
+ -rm -f ./$(DEPDIR)/reconnect_ctl.Plo
+ -rm -f ./$(DEPDIR)/state_model.Plo
+ -rm -f ./$(DEPDIR)/stopwatch.Plo
+ -rm -f ./$(DEPDIR)/stopwatch_impl.Plo
+ -rm -f ./$(DEPDIR)/strutil.Plo
+ -rm -f ./$(DEPDIR)/time_utilities.Plo
+ -rm -f ./$(DEPDIR)/versioned_csv_file.Plo
+ -rm -f ./$(DEPDIR)/watch_socket.Plo
+ -rm -f ./$(DEPDIR)/watched_thread.Plo
+ -rm -f encode/$(DEPDIR)/base_n.Plo
+ -rm -f encode/$(DEPDIR)/utf8.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-libkea_util_encode_includeHEADERS \
+ install-libkea_util_includeHEADERS \
+ install-libkea_util_io_includeHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/boost_time_utils.Plo
+ -rm -f ./$(DEPDIR)/chrono_time_utils.Plo
+ -rm -f ./$(DEPDIR)/csv_file.Plo
+ -rm -f ./$(DEPDIR)/dhcp_space.Plo
+ -rm -f ./$(DEPDIR)/file_utilities.Plo
+ -rm -f ./$(DEPDIR)/filename.Plo
+ -rm -f ./$(DEPDIR)/labeled_value.Plo
+ -rm -f ./$(DEPDIR)/memory_segment_local.Plo
+ -rm -f ./$(DEPDIR)/multi_threading_mgr.Plo
+ -rm -f ./$(DEPDIR)/pid_file.Plo
+ -rm -f ./$(DEPDIR)/reconnect_ctl.Plo
+ -rm -f ./$(DEPDIR)/state_model.Plo
+ -rm -f ./$(DEPDIR)/stopwatch.Plo
+ -rm -f ./$(DEPDIR)/stopwatch_impl.Plo
+ -rm -f ./$(DEPDIR)/strutil.Plo
+ -rm -f ./$(DEPDIR)/time_utilities.Plo
+ -rm -f ./$(DEPDIR)/versioned_csv_file.Plo
+ -rm -f ./$(DEPDIR)/watch_socket.Plo
+ -rm -f ./$(DEPDIR)/watched_thread.Plo
+ -rm -f encode/$(DEPDIR)/base_n.Plo
+ -rm -f encode/$(DEPDIR)/utf8.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES \
+ uninstall-libkea_util_encode_includeHEADERS \
+ uninstall-libkea_util_includeHEADERS \
+ uninstall-libkea_util_io_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES \
+ install-libkea_util_encode_includeHEADERS \
+ install-libkea_util_includeHEADERS \
+ install-libkea_util_io_includeHEADERS install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-libLTLIBRARIES \
+ uninstall-libkea_util_encode_includeHEADERS \
+ uninstall-libkea_util_includeHEADERS \
+ uninstall-libkea_util_io_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/util/bigints.h b/src/lib/util/bigints.h
new file mode 100644
index 0000000..be76b1d
--- /dev/null
+++ b/src/lib/util/bigints.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// This file provides an interface towards bigint implementations.
+// Currently, it uses the ones from boost::multiprecision, but if we ever want
+// to swap it out, or implement our own, we can seamlessly do it in this header.
+
+#ifndef UTIL_BIGINTS_H
+#define UTIL_BIGINTS_H
+
+#include <boost/multiprecision/cpp_int.hpp>
+
+namespace isc {
+namespace util {
+
+using int128_t = boost::multiprecision::int128_t;
+
+using uint128_t = boost::multiprecision::uint128_t;
+
+} // namespace util
+} // namespace isc
+
+#endif // UTIL_BIGINTS_H \ No newline at end of file
diff --git a/src/lib/util/boost_time_utils.cc b/src/lib/util/boost_time_utils.cc
new file mode 100644
index 0000000..3719f72
--- /dev/null
+++ b/src/lib/util/boost_time_utils.cc
@@ -0,0 +1,50 @@
+// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/boost_time_utils.h>
+#include <sstream>
+#include <iomanip>
+
+std::string
+isc::util::ptimeToText(boost::posix_time::ptime t, size_t fsecs_precision) {
+ boost::gregorian::date d = t.date();
+ std::stringstream s;
+ s << d.year()
+ << "-" << std::setw(2) << std::setfill('0') << d.month().as_number()
+ << "-" << std::setw(2) << std::setfill('0') << d.day()
+ << " " << durationToText(t.time_of_day(), fsecs_precision);
+ return (s.str());
+}
+
+std::string
+isc::util::durationToText(boost::posix_time::time_duration dur, size_t fsecs_precision) {
+ std::stringstream s;
+ s << std::setw(2) << std::setfill('0') << dur.hours()
+ << ":" << std::setw(2) << std::setfill('0') << dur.minutes()
+ << ":" << std::setw(2) << std::setfill('0') << dur.seconds();
+
+ // If the requested precision is less than the maximum native precision
+ // we will divide the fractional seconds value by 10^(max - requested)
+ if (fsecs_precision) {
+ size_t fsecs = dur.fractional_seconds();
+ size_t width = MAX_FSECS_PRECISION;
+ if (fsecs_precision < width) {
+ for (auto i = 0; i < width - fsecs_precision; ++i) {
+ fsecs /= 10;
+ }
+
+ width = fsecs_precision;
+ }
+
+ s << "." << std::setw(width)
+ << std::setfill('0')
+ << fsecs;
+ }
+
+ return (s.str());
+}
diff --git a/src/lib/util/boost_time_utils.h b/src/lib/util/boost_time_utils.h
new file mode 100644
index 0000000..a4cdeff
--- /dev/null
+++ b/src/lib/util/boost_time_utils.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef KEA_BOOST_TIME_UTILS_H
+#define KEA_BOOST_TIME_UTILS_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief The number of digits of fractional seconds supplied by the
+/// underlying class, boost::posix_time. Typically 6 = microseconds.
+const size_t MAX_FSECS_PRECISION=boost::posix_time::time_duration::num_fractional_digits();
+
+/// @brief Converts ptime structure to text
+///
+/// This is Kea implementation for converting ptime to strings.
+/// It's a functional equivalent of boost::posix_time::to_simple_string. We do
+/// not use it, though, because it would introduce unclear dependency on
+/// boost_time_date library. First, we try to avoid depending on boost libraries
+/// (we tend to use only the headers). Second, this dependency is system
+/// specific, i.e. it is required on Ubuntu and FreeBSD, but doesn't seem to
+/// be needed on OS X. Since the functionality needed is minor, we decided to
+/// reimplement it on our own, rather than introduce extra dependencies.
+/// This explanation also applies to @ref durationToText.
+/// @param t ptime value to convert to text
+/// @param fsecs_precision number of digits of precision for fractional seconds.
+/// Default is given by boost::posix_time::time_duration::num_fractional_digits().
+/// Zero omits the value.
+///
+/// @return a string representing time
+std::string ptimeToText(boost::posix_time::ptime t,
+ size_t fsecs_precision = MAX_FSECS_PRECISION);
+
+/// @brief Converts StatsDuration to text
+///
+/// This is Kea equivalent of boost::posix_time::to_simple_string(time_duration).
+/// See @ref ptimeToText for explanation why we chose our own implementation.
+/// @param dur duration value to convert to text
+/// @param fsecs_precision number of digits of precision for fractional seconds.
+/// Default is given by boost::posix_time::time_duration::num_fractional_digits().
+/// Zero omits the value.
+///
+/// @return a string representing time
+std::string durationToText(boost::posix_time::time_duration dur,
+ size_t fsecs_precision = MAX_FSECS_PRECISION);
+
+}; // end of isc::util namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/util/buffer.h b/src/lib/util/buffer.h
new file mode 100644
index 0000000..4f91856
--- /dev/null
+++ b/src/lib/util/buffer.h
@@ -0,0 +1,611 @@
+// Copyright (C) 2009-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BUFFER_H
+#define BUFFER_H 1
+
+#include <stdlib.h>
+#include <cstring>
+#include <vector>
+
+#include <stdint.h>
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace util {
+
+///
+/// \brief A standard DNS module exception that is thrown if an out-of-range
+/// buffer operation is being performed.
+///
+class InvalidBufferPosition : public Exception {
+public:
+ InvalidBufferPosition(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///\brief The \c InputBuffer class is a buffer abstraction for manipulating
+/// read-only data.
+///
+/// The main purpose of this class is to provide a safe placeholder for
+/// examining wire-format data received from a network.
+///
+/// Applications normally use this class only in a limited situation: as an
+/// interface between legacy I/O operation (such as receiving data from a BSD
+/// socket) and the rest of the Kea DNS library. One common usage of this
+/// class for an application would therefore be something like this:
+///
+/// \code unsigned char buf[1024];
+/// struct sockaddr addr;
+/// socklen_t addrlen = sizeof(addr);
+/// int cc = recvfrom(s, buf, sizeof(buf), 0, &addr, &addrlen);
+/// InputBuffer buffer(buf, cc);
+/// // pass the buffer to a DNS message object to parse the message \endcode
+///
+/// Other Kea DNS classes will then use methods of this class to get access
+/// to the data, but the application normally doesn't have to care about the
+/// details.
+///
+/// An \c InputBuffer object internally holds a reference to the given data,
+/// rather than make a local copy of the data. Also, it does not have an
+/// ownership of the given data. It is application's responsibility to ensure
+/// the data remains valid throughout the lifetime of the \c InputBuffer
+/// object. Likewise, this object generally assumes the data isn't modified
+/// throughout its lifetime; if the application modifies the data while this
+/// object retains a reference to it, the result is undefined. The application
+/// will also be responsible for releasing the data when it's not needed if it
+/// was dynamically acquired.
+///
+/// This is a deliberate design choice: although it's safer to make a local
+/// copy of the given data on construction, it would cause unacceptable
+/// performance overhead, especially considering that a DNS message can be
+/// as large as a few KB. Alternatively, we could allow the object to allocate
+/// memory internally and expose it to the application to store network data
+/// in it. This is also a bad design, however, in that we would effectively
+/// break the abstraction employed in the class, and do so by publishing
+/// "read-only" stuff as a writable memory region. Since there doesn't seem to
+/// be a perfect solution, we have adopted what we thought a "least bad" one.
+///
+/// Methods for reading data from the buffer generally work like an input
+/// stream: it begins with the head of the data, and once some length of data
+/// is read from the buffer, the next read operation will take place from the
+/// head of the unread data. An object of this class internally holds (a
+/// notion of) where the next read operation should start. We call it the
+/// <em>read position</em> in this document.
+class InputBuffer {
+public:
+ ///
+ /// \name Constructors and Destructor
+ //@{
+ /// \brief Constructor from variable length of data.
+ ///
+ /// It is caller's responsibility to ensure that the data is valid as long
+ /// as the buffer exists.
+ /// \param data A pointer to the data stored in the buffer.
+ /// \param len The length of the data in bytes.
+ InputBuffer(const void* data, size_t len) :
+ position_(0), data_(static_cast<const uint8_t*>(data)), len_(len) {}
+ //@}
+
+ ///
+ /// \name Getter Methods
+ //@{
+ /// \brief Return the length of the data stored in the buffer.
+ size_t getLength() const { return (len_); }
+ /// \brief Return the current read position.
+ size_t getPosition() const { return (position_); }
+ //@}
+
+ ///
+ /// \name Setter Methods
+ ///
+ //@{
+ /// \brief Set the read position of the buffer to the given value.
+ ///
+ /// The new position must be in the valid range of the buffer; otherwise
+ /// an exception of class \c isc::dns::InvalidBufferPosition will be thrown.
+ /// \param position The new position (offset from the beginning of the
+ /// buffer).
+ void setPosition(size_t position) {
+ if (position > len_) {
+ throwError("position is too large");
+ }
+ position_ = position;
+ }
+ //@}
+
+ ///
+ /// \name Methods for reading data from the buffer.
+ //@{
+ /// \brief Read an unsigned 8-bit integer from the buffer and return it.
+ ///
+ /// If the remaining length of the buffer is smaller than 8-bit, an
+ /// exception of class \c isc::dns::InvalidBufferPosition will be thrown.
+ uint8_t readUint8() {
+ if (position_ + sizeof(uint8_t) > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ return (data_[position_++]);
+ }
+ /// \brief Read an unsigned 16-bit integer in network byte order from the
+ /// buffer, convert it to host byte order, and return it.
+ ///
+ /// If the remaining length of the buffer is smaller than 16-bit, an
+ /// exception of class \c isc::dns::InvalidBufferPosition will be thrown.
+ uint16_t readUint16() {
+ uint16_t data;
+ const uint8_t* cp;
+
+ if (position_ + sizeof(data) > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ cp = &data_[position_];
+ data = ((unsigned int)(cp[0])) << 8;
+ data |= ((unsigned int)(cp[1]));
+ position_ += sizeof(data);
+
+ return (data);
+ }
+ /// \brief Read an unsigned 32-bit integer in network byte order from the
+ /// buffer, convert it to host byte order, and return it.
+ ///
+ /// If the remaining length of the buffer is smaller than 32-bit, an
+ /// exception of class \c isc::dns::InvalidBufferPosition will be thrown.
+ uint32_t readUint32() {
+ uint32_t data;
+ const uint8_t* cp;
+
+ if (position_ + sizeof(data) > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ cp = &data_[position_];
+ data = ((unsigned int)(cp[0])) << 24;
+ data |= ((unsigned int)(cp[1])) << 16;
+ data |= ((unsigned int)(cp[2])) << 8;
+ data |= ((unsigned int)(cp[3]));
+ position_ += sizeof(data);
+
+ return (data);
+ }
+ /// \brief Read data of the specified length from the buffer and copy it to
+ /// the caller supplied buffer.
+ ///
+ /// The data is copied as stored in the buffer; no conversion is performed.
+ /// If the remaining length of the buffer is smaller than the specified
+ /// length, an exception of class \c isc::dns::InvalidBufferPosition will
+ /// be thrown.
+ void readData(void* data, size_t len) {
+ if (position_ + len > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ static_cast<void>(std::memmove(data, &data_[position_], len));
+ position_ += len;
+ }
+ //@}
+
+ /// @brief Read specified number of bytes as a vector.
+ ///
+ /// If specified buffer is too short, it will be expanded
+ /// using vector::resize() method.
+ ///
+ /// @param data Reference to a buffer (data will be stored there).
+ /// @param len Size specified number of bytes to read in a vector.
+ ///
+ void readVector(std::vector<uint8_t>& data, size_t len) {
+ if (position_ + len > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ data.resize(len);
+ readData(&data[0], len);
+ }
+
+private:
+ /// \brief A common helper to throw an exception on invalid operation.
+ ///
+ /// Experiments showed that throwing from each method makes the buffer
+ /// operation slower, so we consolidate it here, and let the methods
+ /// call this.
+ static void throwError(const char* msg) {
+ isc_throw(InvalidBufferPosition, msg);
+ }
+
+ size_t position_;
+
+ // XXX: The following must be private, but for a short term workaround with
+ // Boost.Python binding, we changed it to protected. We should soon
+ // revisit it.
+protected:
+ const uint8_t* data_;
+ size_t len_;
+};
+
+///
+///\brief The \c OutputBuffer class is a buffer abstraction for manipulating
+/// mutable data.
+///
+/// The main purpose of this class is to provide a safe workplace for
+/// constructing wire-format data to be sent out to a network. Here,
+/// <em>safe</em> means that it automatically allocates necessary memory and
+/// avoid buffer overrun.
+///
+/// Like for the \c InputBuffer class, applications normally use this class only
+/// in a limited situation. One common usage of this class for an application
+/// would be something like this:
+///
+/// \code OutputBuffer buffer(4096); // give a sufficiently large initial size
+/// // pass the buffer to a DNS message object to construct a wire-format
+/// // DNS message.
+/// struct sockaddr to;
+/// sendto(s, buffer.getData(), buffer.getLength(), 0, &to, sizeof(to));
+/// \endcode
+///
+/// where the \c getData() method gives a reference to the internal memory
+/// region stored in the \c buffer object. This is a suboptimal design in that
+/// it exposes an encapsulated "handle" of an object to its user.
+/// Unfortunately, there is no easy way to avoid this without involving
+/// expensive data copy if we want to use this object with a legacy API such as
+/// a BSD socket interface. And, indeed, this is one major purpose for this
+/// object. Applications should use this method only under such a special
+/// circumstance. It should also be noted that the memory region returned by
+/// \c getData() may be invalidated after a subsequent write operation.
+///
+/// An \c OutputBuffer class object automatically extends its memory region when
+/// data is written beyond the end of the current buffer. However, it will
+/// involve performance overhead such as reallocating more memory and copying
+/// data. It is therefore recommended to construct the buffer object with a
+/// sufficiently large initial size.
+/// The \c getCapacity() method provides the current maximum size of data
+/// (including the portion already written) that can be written into the buffer
+/// without causing memory reallocation.
+///
+/// Methods for writing data into the buffer generally work like an output
+/// stream: it begins with the head of the buffer, and once some length of data
+/// is written into the buffer, the next write operation will take place from
+/// the end of the buffer. Other methods to emulate "random access" are also
+/// provided (e.g., \c writeUint16At()). The normal write operations are
+/// normally exception-free as this class automatically extends the buffer
+/// when necessary. However, in extreme cases such as an attempt of writing
+/// multi-GB data, a separate exception (e.g., \c std::bad_alloc) may be thrown
+/// by the system. This also applies to the constructor with a very large
+/// initial size.
+///
+/// Note to developers: it may make more sense to introduce an abstract base
+/// class for the \c OutputBuffer and define the simple implementation as a
+/// concrete derived class. That way we can provide flexibility for future
+/// extension such as more efficient buffer implementation or allowing users
+/// to have their own customized version without modifying the source code.
+/// We in fact considered that option, but at the moment chose the simpler
+/// approach with a single concrete class because it may make the
+/// implementation unnecessarily complicated while we were still not certain
+/// if we really want that flexibility. We may revisit the class design as
+/// we see more applications of the class. The same considerations apply to
+/// the \c InputBuffer and \c MessageRenderer classes.
+class OutputBuffer {
+public:
+ ///
+ /// \name Constructors and Destructor
+ ///
+ //@{
+ /// \brief Constructor from the initial size of the buffer.
+ ///
+ /// \param len The initial length of the buffer in bytes.
+ OutputBuffer(size_t len) :
+ buffer_(NULL),
+ size_(0),
+ allocated_(len)
+ {
+ // We use malloc and free instead of C++ new[] and delete[].
+ // This way we can use realloc, which may in fact do it without a copy.
+ if (allocated_ != 0) {
+ buffer_ = static_cast<uint8_t*>(malloc(allocated_));
+ if (buffer_ == NULL) {
+ throw std::bad_alloc();
+ }
+ }
+ }
+
+ /// \brief Copy constructor
+ ///
+ /// \param other Source object from which to make a copy.
+ ///
+ /// \note It is assumed that the source object is consistent, i.e.
+ /// size_ <= allocated_, and that if allocated_ is greater than zero,
+ /// buffer_ points to valid memory.
+ OutputBuffer(const OutputBuffer& other) :
+ buffer_(NULL),
+ size_(other.size_),
+ allocated_(other.allocated_)
+ {
+ if (allocated_ != 0) {
+ buffer_ = static_cast<uint8_t*>(malloc(allocated_));
+ if (buffer_ == NULL) {
+ throw std::bad_alloc();
+ }
+ static_cast<void>(std::memmove(buffer_, other.buffer_, other.size_));
+ }
+ }
+
+ /// \brief Destructor
+ ~OutputBuffer() {
+ free(buffer_);
+ }
+ //@}
+
+ /// \brief Assignment operator
+ ///
+ /// \param other Object to copy into "this".
+ ///
+ /// \note It is assumed that the source object is consistent, i.e.
+ /// size_ <= allocated_, and that if allocated_ is greater than zero,
+ /// buffer_ points to valid memory.
+ OutputBuffer& operator =(const OutputBuffer& other) {
+ if (this != &other) {
+ // Not self-assignment.
+ if (other.allocated_ != 0) {
+
+ // There is something in the source object, so allocate memory
+ // and copy it. The pointer to the allocated memory is placed
+ // in a temporary variable so that if the allocation fails and
+ // an exception is thrown, the destination object ("this") is
+ // unchanged.
+ uint8_t* newbuff = static_cast<uint8_t*>(malloc(other.allocated_));
+ if (newbuff == NULL) {
+ throw std::bad_alloc();
+ }
+
+ // Memory allocated, update the source object and copy data
+ // across.
+ free(buffer_);
+ buffer_ = newbuff;
+ static_cast<void>(std::memmove(buffer_, other.buffer_, other.size_));
+
+ } else {
+
+ // Nothing allocated in the source object, so zero the buffer
+ // in the destination.
+ free(buffer_);
+ buffer_ = NULL;
+ }
+
+ // Update the other member variables.
+ size_ = other.size_;
+ allocated_ = other.allocated_;
+ }
+ return (*this);
+ }
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Return the current capacity of the buffer.
+ size_t getCapacity() const { return (allocated_); }
+ /// \brief Return a pointer to the head of the data stored in the buffer.
+ ///
+ /// The caller can assume that the subsequent \c getLength() bytes are
+ /// identical to the stored data of the buffer.
+ ///
+ /// Note: The pointer returned by this method may be invalidated after a
+ /// subsequent write operation.
+ const void* getData() const { return (buffer_); }
+ /// \brief Return the length of data written in the buffer.
+ size_t getLength() const { return (size_); }
+ /// \brief Return the value of the buffer at the specified position.
+ ///
+ /// \c pos must specify the valid position of the buffer; otherwise an
+ /// exception class of \c InvalidBufferPosition will be thrown.
+ ///
+ /// \param pos The position in the buffer to be returned.
+ uint8_t operator[](size_t pos) const {
+ if (pos >= size_) {
+ isc_throw(InvalidBufferPosition,
+ "[]: pos (" << pos << ") >= size (" << size_ << ")");
+ }
+ return (buffer_[pos]);
+ }
+ //@}
+
+ ///
+ /// \name Methods for writing data into the buffer.
+ ///
+ //@{
+ /// \brief Insert a specified length of gap at the end of the buffer.
+ ///
+ /// The caller should not assume any particular value to be inserted.
+ /// This method is provided as a shortcut to make a hole in the buffer
+ /// that is to be filled in later, e.g, by \ref writeUint16At().
+ /// \param len The length of the gap to be inserted in bytes.
+ void skip(size_t len) {
+ ensureAllocated(size_ + len);
+ size_ += len;
+ }
+
+ /// \brief Trim the specified length of data from the end of the buffer.
+ ///
+ /// The specified length must not exceed the current data size of the
+ /// buffer; otherwise an exception of class \c isc::OutOfRange will
+ /// be thrown.
+ ///
+ /// \param len The length of data that should be trimmed.
+ void trim(size_t len) {
+ if (len > size_) {
+ isc_throw(OutOfRange, "trimming too large from output buffer");
+ }
+ size_ -= len;
+ }
+ /// \brief Clear buffer content.
+ ///
+ /// This method can be used to re-initialize and reuse the buffer without
+ /// constructing a new one. Note it must keep current content.
+ void clear() { size_ = 0; }
+
+ /// \brief Wipe buffer content.
+ ///
+ /// This method is the destructive alternative to clear().
+ void wipe() {
+ if (buffer_ != NULL) {
+ static_cast<void>(std::memset(buffer_, 0, allocated_));
+ }
+ size_ = 0;
+ }
+
+ /// \brief Write an unsigned 8-bit integer into the buffer.
+ ///
+ /// \param data The 8-bit integer to be written into the buffer.
+ void writeUint8(uint8_t data) {
+ ensureAllocated(size_ + 1);
+ buffer_[size_ ++] = data;
+ }
+
+ /// \brief Write an unsigned 8-bit integer into the buffer.
+ ///
+ /// The position must be lower than the size of the buffer,
+ /// otherwise an exception of class \c isc::dns::InvalidBufferPosition
+ /// will be thrown.
+ ///
+ /// \param data The 8-bit integer to be written into the buffer.
+ /// \param pos The position in the buffer to write the data.
+ void writeUint8At(uint8_t data, size_t pos) {
+ if (pos + sizeof(data) > size_) {
+ isc_throw(InvalidBufferPosition, "write at invalid position");
+ }
+ buffer_[pos] = data;
+ }
+
+ /// \brief Write an unsigned 16-bit integer in host byte order into the
+ /// buffer in network byte order.
+ ///
+ /// \param data The 16-bit integer to be written into the buffer.
+ void writeUint16(uint16_t data) {
+ ensureAllocated(size_ + sizeof(data));
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0xff00U) >> 8);
+ buffer_[size_ ++] = static_cast<uint8_t>(data & 0x00ffU);
+ }
+
+ /// \brief Write an unsigned 16-bit integer in host byte order at the
+ /// specified position of the buffer in network byte order.
+ ///
+ /// The buffer must have a sufficient room to store the given data at the
+ /// given position, that is, <code>pos + 2 < getLength()</code>;
+ /// otherwise an exception of class \c isc::dns::InvalidBufferPosition will
+ /// be thrown.
+ /// Note also that this method never extends the buffer.
+ ///
+ /// \param data The 16-bit integer to be written into the buffer.
+ /// \param pos The beginning position in the buffer to write the data.
+ void writeUint16At(uint16_t data, size_t pos) {
+ if (pos + sizeof(data) > size_) {
+ isc_throw(InvalidBufferPosition, "write at invalid position");
+ }
+
+ buffer_[pos] = static_cast<uint8_t>((data & 0xff00U) >> 8);
+ buffer_[pos + 1] = static_cast<uint8_t>(data & 0x00ffU);
+ }
+
+ /// \brief Write an unsigned 32-bit integer in host byte order
+ /// into the buffer in network byte order.
+ ///
+ /// \param data The 32-bit integer to be written into the buffer.
+ void writeUint32(uint32_t data) {
+ ensureAllocated(size_ + sizeof(data));
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0xff000000) >> 24);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x00ff0000) >> 16);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x0000ff00) >> 8);
+ buffer_[size_ ++] = static_cast<uint8_t>(data & 0x000000ff);
+ }
+
+ /// \brief Write an unsigned 64-bit integer in host byte order
+ /// into the buffer in network byte order.
+ ///
+ /// \param data The 64-bit integer to be written into the buffer.
+ void writeUint64(uint64_t data) {
+ ensureAllocated(size_ + sizeof(data));
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0xff00000000000000) >> 56);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x00ff000000000000) >> 48);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x0000ff0000000000) >> 40);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x000000ff00000000) >> 32);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x00000000ff000000) >> 24);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x0000000000ff0000) >> 16);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x000000000000ff00) >> 8);
+ buffer_[size_ ++] = static_cast<uint8_t>(data & 0x00000000000000ff);
+ }
+
+ /// \brief Copy an arbitrary length of data into the buffer.
+ ///
+ /// No conversion on the copied data is performed.
+ ///
+ /// \param data A pointer to the data to be copied into the buffer.
+ /// \param len The length of the data in bytes.
+ void writeData(const void *data, size_t len) {
+ if (len == 0) {
+ return;
+ }
+
+ ensureAllocated(size_ + len);
+ static_cast<void>(std::memmove(buffer_ + size_, data, len));
+ size_ += len;
+ }
+ //@}
+
+private:
+ /// The actual data
+ uint8_t* buffer_;
+ /// How many bytes are used
+ size_t size_;
+ /// How many bytes do we have preallocated (eg. the capacity)
+ size_t allocated_;
+
+ /// \brief Ensure buffer is appropriate size
+ ///
+ /// Checks that the buffer equal to or larger than the size given as
+ /// argument and extends it to at least that size if not.
+ ///
+ /// \param needed_size The number of bytes required in the buffer
+ void ensureAllocated(size_t needed_size) {
+ if (allocated_ < needed_size) {
+ // Guess some bigger size
+ size_t new_size = (allocated_ == 0) ? 1024 : allocated_;
+ while (new_size < needed_size) {
+ new_size *= 2;
+ }
+ // Allocate bigger space. Note that buffer_ may be NULL,
+ // in which case realloc acts as malloc.
+ uint8_t* new_buffer_(static_cast<uint8_t*>(realloc(buffer_,
+ new_size)));
+ if (new_buffer_ == NULL) {
+ // If it fails, the original block is left intact by it
+ throw std::bad_alloc();
+ }
+ buffer_ = new_buffer_;
+ allocated_ = new_size;
+ }
+ }
+};
+
+/// \brief Pointer-like types pointing to \c InputBuffer or \c OutputBuffer
+///
+/// These types are expected to be used as an argument in asynchronous
+/// callback functions. The internal reference-counting will ensure that
+/// that ongoing state information will not be lost if the object
+/// that originated the asynchronous call falls out of scope.
+typedef boost::shared_ptr<InputBuffer> InputBufferPtr;
+typedef boost::shared_ptr<OutputBuffer> OutputBufferPtr;
+
+} // namespace util
+} // namespace isc
+#endif // BUFFER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/chrono_time_utils.cc b/src/lib/util/chrono_time_utils.cc
new file mode 100644
index 0000000..6c76112
--- /dev/null
+++ b/src/lib/util/chrono_time_utils.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/chrono_time_utils.h>
+#include <sstream>
+#include <iomanip>
+
+using namespace std::chrono;
+
+namespace isc {
+namespace util {
+
+std::string
+clockToText(std::chrono::system_clock::time_point t, size_t fsecs_precision) {
+ time_t tt = system_clock::to_time_t(t);
+ struct tm tm;
+ localtime_r(&tt, &tm);
+ std::stringstream s;
+ s << (tm.tm_year + 1900)
+ << "-" << std::setw(2) << std::setfill('0') << (tm.tm_mon + 1)
+ << "-" << std::setw(2) << std::setfill('0') << tm.tm_mday
+ << " " << std::setw(2) << std::setfill('0') << tm.tm_hour
+ << ":" << std::setw(2) << std::setfill('0') << tm.tm_min
+ << ":" << std::setw(2) << std::setfill('0') << tm.tm_sec;
+
+ // If the requested precision is less than the maximum native precision
+ // we will divide the fractional seconds value by 10^(max - requested)
+ if (fsecs_precision) {
+ system_clock::duration dur = t - system_clock::from_time_t(tt);
+ microseconds frac = duration_cast<microseconds>(dur);
+ auto fsecs = frac.count();
+ size_t width = MAX_FSECS_PRECISION;
+ if (fsecs_precision < width) {
+ for (auto i = 0; i < width - fsecs_precision; ++i) {
+ fsecs /= 10;
+ }
+
+ width = fsecs_precision;
+ }
+
+ s << "." << std::setw(width)
+ << std::setfill('0')
+ << fsecs;
+ }
+
+ return (s.str());
+}
+
+template<typename Duration> std::string
+durationToText(Duration dur, size_t fsecs_precision) {
+ seconds unfrac = duration_cast<seconds>(dur);
+ auto secs = unfrac.count();
+ std::stringstream s;
+ auto hours = secs / 3600;
+ secs -= hours * 3600;
+ s << std::setw(2) << std::setfill('0') << hours;
+ auto mins = secs / 60;
+ secs -= mins * 60;
+ s << ":" << std::setw(2) << std::setfill('0') << mins
+ << ":" << std::setw(2) << std::setfill('0') << secs;
+
+ // If the requested precision is less than the maximum native precision
+ // we will divide the fractional seconds value by 10^(max - requested)
+ if (fsecs_precision) {
+ microseconds frac = duration_cast<microseconds>(dur);
+ frac -= duration_cast<microseconds>(unfrac);
+ auto fsecs = frac.count();
+ size_t width = MAX_FSECS_PRECISION;
+ if (fsecs_precision < width) {
+ for (auto i = 0; i < width - fsecs_precision; ++i) {
+ fsecs /= 10;
+ }
+
+ width = fsecs_precision;
+ }
+
+ s << "." << std::setw(width)
+ << std::setfill('0')
+ << fsecs;
+ }
+
+ return (s.str());
+}
+
+// Instantiate for standard clocks.
+template std::string
+durationToText<system_clock::duration>(system_clock::duration dur,
+ size_t fsecs_precision);
+
+#if !CHRONO_SAME_DURATION
+template std::string
+durationToText<steady_clock::duration>(steady_clock::duration dur,
+ size_t fsecs_precision);
+#endif
+
+} // end of isc::util namespace
+} // end of isc namespace
diff --git a/src/lib/util/chrono_time_utils.h b/src/lib/util/chrono_time_utils.h
new file mode 100644
index 0000000..3ef0848
--- /dev/null
+++ b/src/lib/util/chrono_time_utils.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef KEA_CHRONO_TIME_UTILS_H
+#define KEA_CHRONO_TIME_UTILS_H
+
+#include <chrono>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief The number of digits of fractional seconds supplied by the
+/// underlying class, std::chrono::time_point. Typically 6 = microseconds.
+const size_t MAX_FSECS_PRECISION = 6;
+
+/// @brief Converts chrono time point structure to text
+///
+/// This is Kea implementation for converting time point to strings.
+/// @param t time point value to convert to text
+/// @param fsecs_precision number of digits of precision for fractional seconds.
+/// Zero omits the value.
+///
+/// @return a string representing time
+std::string clockToText(std::chrono::system_clock::time_point t,
+ size_t fsecs_precision = MAX_FSECS_PRECISION);
+
+/// @brief Converts StatsDuration to text
+///
+/// See @ref clockToText for explanation why we chose our own implementation.
+/// @tparam Duration duration type instance for instance
+/// @c std::chrono::system_clock::duration.
+/// @param dur duration value to convert to text
+/// @param fsecs_precision number of digits of precision for fractional seconds.
+/// Zero omits the value.
+///
+/// @return a string representing time
+template<typename Duration>
+std::string durationToText(Duration dur,
+ size_t fsecs_precision = MAX_FSECS_PRECISION);
+
+} // end of isc::util namespace
+} // end of isc namespace
+
+#endif
diff --git a/src/lib/util/csv_file.cc b/src/lib/util/csv_file.cc
new file mode 100644
index 0000000..f402038
--- /dev/null
+++ b/src/lib/util/csv_file.cc
@@ -0,0 +1,557 @@
+// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <util/csv_file.h>
+
+#include <algorithm>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <iomanip>
+
+namespace isc {
+namespace util {
+
+CSVRow::CSVRow(const size_t cols, const char separator)
+ : separator_(1, separator), values_(cols) {
+}
+
+CSVRow::CSVRow(const std::string& text, const char separator)
+ : separator_(1, separator) {
+ // Parsing is exception safe, so this will not throw.
+ parse(text);
+}
+
+void
+CSVRow::parse(const std::string& line) {
+ size_t sep_pos = 0;
+ size_t prev_pos = 0;
+ size_t len = 0;
+
+ // In case someone is reusing the row.
+ values_.clear();
+
+ // Iterate over line, splitting on separators.
+ while (prev_pos < line.size()) {
+ // Find the next separator.
+ sep_pos = line.find_first_of(separator_, prev_pos);
+ if (sep_pos == std::string::npos) {
+ break;
+ }
+
+ // Extract the value for the previous column.
+ len = sep_pos - prev_pos;
+ values_.push_back(line.substr(prev_pos, len));
+
+ // Move past the separator.
+ prev_pos = sep_pos + 1;
+ };
+
+ // Extract the last column.
+ len = line.size() - prev_pos;
+ values_.push_back(line.substr(prev_pos, len));
+}
+
+std::string
+CSVRow::readAt(const size_t at) const {
+ checkIndex(at);
+ return (values_[at]);
+}
+
+std::string
+CSVRow::readAtEscaped(const size_t at) const {
+ return (unescapeCharacters(readAt(at)));
+}
+
+std::string
+CSVRow::render() const {
+ std::ostringstream s;
+ for (size_t i = 0; i < values_.size(); ++i) {
+ // Do not put separator before the first value.
+ if (i > 0) {
+ s << separator_;
+ }
+ s << values_[i];
+ }
+ return (s.str());
+}
+
+void
+CSVRow::writeAt(const size_t at, const char* value) {
+ checkIndex(at);
+ values_[at] = value;
+}
+
+void
+CSVRow::writeAtEscaped(const size_t at, const std::string& value) {
+ writeAt(at, escapeCharacters(value, separator_));
+}
+
+void
+CSVRow::trim(const size_t count) {
+ checkIndex(count);
+ values_.resize(values_.size() - count);
+}
+
+std::ostream& operator<<(std::ostream& os, const CSVRow& row) {
+ os << row.render();
+ return (os);
+}
+
+void
+CSVRow::checkIndex(const size_t at) const {
+ if (at >= values_.size()) {
+ isc_throw(CSVFileError, "value index '" << at << "' of the CSV row"
+ " is out of bounds; maximal index is '"
+ << (values_.size() - 1) << "'");
+ }
+}
+
+CSVFile::CSVFile(const std::string& filename)
+ : filename_(filename), fs_(), cols_(0), read_msg_() {
+}
+
+CSVFile::~CSVFile() {
+ close();
+}
+
+void
+CSVFile::close() {
+ // It is allowed to close multiple times. If file has been already closed,
+ // this is no-op.
+ if (fs_) {
+ fs_->close();
+ fs_.reset();
+ }
+}
+
+bool
+CSVFile::exists() const {
+ std::ifstream fs(filename_.c_str());
+ const bool file_exists = fs.good();
+ fs.close();
+ return (file_exists);
+}
+
+void
+CSVFile::flush() const {
+ checkStreamStatusAndReset("flush");
+ fs_->flush();
+}
+
+void
+CSVFile::addColumn(const std::string& col_name) {
+ // It is not allowed to add a new column when file is open.
+ if (fs_) {
+ isc_throw(CSVFileError, "attempt to add a column '" << col_name
+ << "' while the file '" << getFilename()
+ << "' is open");
+ }
+ addColumnInternal(col_name);
+}
+
+void
+CSVFile::addColumnInternal(const std::string& col_name) {
+ if (std::find(cols_.begin(), cols_.end(), col_name) != cols_.end()) {
+ isc_throw(CSVFileError, "attempt to add duplicate column '"
+ << col_name << "'");
+ }
+ cols_.push_back(col_name);
+}
+
+void
+CSVFile::append(const CSVRow& row) const {
+ checkStreamStatusAndReset("append");
+
+ if (row.getValuesCount() != getColumnCount()) {
+ isc_throw(CSVFileError, "number of values in the CSV row '"
+ << row.getValuesCount() << "' doesn't match the number of"
+ " columns in the CSV file '" << getColumnCount() << "'");
+ }
+
+ /// @todo Apparently, seekp and seekg are interchangeable. A call to seekp
+ /// results in moving the input pointer too. This is ok for now. It means
+ /// that when the append() is called, the read pointer is moved to the EOF.
+ /// For the current use cases we only read a file and then append a new
+ /// content. If we come up with the scenarios when read and write is
+ /// needed at the same time, we may revisit this: perhaps remember the
+ /// old pointer. Also, for safety, we call both functions so as we are
+ /// sure that both pointers are moved.
+ fs_->seekp(0, std::ios_base::end);
+ fs_->seekg(0, std::ios_base::end);
+ fs_->clear();
+
+ std::string text = row.render();
+ *fs_ << text << std::endl;
+ if (!fs_->good()) {
+ fs_->clear();
+ isc_throw(CSVFileError, "failed to write CSV row '"
+ << text << "' to the file '" << filename_ << "'");
+ }
+}
+
+void
+CSVFile::checkStreamStatusAndReset(const std::string& operation) const {
+ if (!fs_) {
+ isc_throw(CSVFileError, "NULL stream pointer when performing '"
+ << operation << "' on file '" << filename_ << "'");
+
+ } else if (!fs_->is_open()) {
+ fs_->clear();
+ isc_throw(CSVFileError, "closed stream when performing '"
+ << operation << "' on file '" << filename_ << "'");
+
+ } else {
+ fs_->clear();
+ }
+}
+
+std::streampos
+CSVFile::size() const {
+ std::ifstream fs(filename_.c_str());
+ bool ok = fs.good();
+ // If something goes wrong, including that the file doesn't exist,
+ // return 0.
+ if (!ok) {
+ fs.close();
+ return (0);
+ }
+ std::ifstream::pos_type pos;
+ try {
+ // Seek to the end of file and see where we are. This is a size of
+ // the file.
+ fs.seekg(0, std::ifstream::end);
+ pos = fs.tellg();
+ fs.close();
+ } catch (const std::exception&) {
+ return (0);
+ }
+ return (pos);
+}
+
+size_t
+CSVFile::getColumnIndex(const std::string& col_name) const {
+ for (size_t i = 0; i < cols_.size(); ++i) {
+ if (cols_[i] == col_name) {
+ return (i);
+ }
+ }
+ isc_throw(isc::OutOfRange, "column '" << col_name << "' doesn't exist");
+}
+
+std::string
+CSVFile::getColumnName(const size_t col_index) const {
+ if (col_index >= cols_.size()) {
+ isc_throw(isc::OutOfRange, "column index " << col_index << " in the "
+ " CSV file '" << filename_ << "' is out of range; the CSV"
+ " file has only " << cols_.size() << " columns ");
+ }
+ return (cols_[col_index]);
+}
+
+bool
+CSVFile::next(CSVRow& row, const bool skip_validation) {
+ // Set something as row validation error. Although, we haven't started
+ // actual row validation we should get rid of any previously recorded
+ // errors so as the caller doesn't interpret them as the current one.
+ setReadMsg("validation not started");
+
+ try {
+ // Check that stream is "ready" for any IO operations.
+ checkStreamStatusAndReset("get next row");
+
+ } catch (const isc::Exception& ex) {
+ setReadMsg(ex.what());
+ return (false);
+ }
+
+ // Get the next non-blank line from the file.
+ std::string line;
+ while (fs_->good() && line.empty()) {
+ std::getline(*fs_, line);
+ }
+
+ // If we didn't read anything...
+ if (line.empty()) {
+ // If we reached the end of file, return an empty row to signal EOF.
+ if (fs_->eof()) {
+ row = EMPTY_ROW();
+ return (true);
+
+ } else if (!fs_->good()) {
+ // If we hit an IO error, communicate it to the caller but do NOT close
+ // the stream. Caller may try again.
+ setReadMsg("error reading a row from CSV file '"
+ + std::string(filename_) + "'");
+ return (false);
+ }
+ }
+
+ // Parse the line.
+ row.parse(line);
+
+ // And check if it is correct.
+ return (skip_validation ? true : validate(row));
+}
+
+void
+CSVFile::open(const bool seek_to_end) {
+ // If file doesn't exist or is empty, we have to create our own file.
+ if (size() == static_cast<std::streampos>(0)) {
+ recreate();
+
+ } else {
+ // Try to open existing file, holding some data.
+ fs_.reset(new std::fstream(filename_.c_str()));
+
+ // Catch exceptions so as we can close the file if error occurs.
+ try {
+ // The file may fail to open. For example, because of insufficient
+ // permissions. Although the file is not open we should call close
+ // to reset our internal pointer.
+ if (!fs_->is_open()) {
+ isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
+ }
+ // Make sure we are on the beginning of the file, so as we
+ // can parse the header.
+ fs_->seekg(0);
+ if (!fs_->good()) {
+ isc_throw(CSVFileError, "unable to set read pointer in the file '"
+ << filename_ << "'");
+ }
+
+ // Read the header.
+ CSVRow header;
+ if (!next(header, true)) {
+ isc_throw(CSVFileError, "failed to read and parse header of the"
+ " CSV file '" << filename_ << "': "
+ << getReadMsg());
+ }
+
+ // Check the header against the columns specified for the CSV file.
+ if (!validateHeader(header)) {
+ isc_throw(CSVFileError, "invalid header '" << header
+ << "' in CSV file '" << filename_ << "': "
+ << getReadMsg());
+ }
+
+ // Everything is good, so if we haven't added any columns yet,
+ // add them.
+ if (getColumnCount() == 0) {
+ for (size_t i = 0; i < header.getValuesCount(); ++i) {
+ addColumnInternal(header.readAt(i));
+ }
+ }
+
+ // If caller requested that the pointer is set at the end of file,
+ // move both read and write pointer.
+ if (seek_to_end) {
+ fs_->seekp(0, std::ios_base::end);
+ fs_->seekg(0, std::ios_base::end);
+ if (!fs_->good()) {
+ isc_throw(CSVFileError, "unable to move to the end of"
+ " CSV file '" << filename_ << "'");
+ }
+ fs_->clear();
+ }
+
+ } catch (const std::exception&) {
+ close();
+ throw;
+ }
+ }
+}
+
+void
+CSVFile::recreate() {
+ // There is no sense creating a file if we don't specify columns for it.
+ if (getColumnCount() == 0) {
+ close();
+ isc_throw(CSVFileError, "no columns defined for the newly"
+ " created CSV file '" << filename_ << "'");
+ }
+
+ // Close any dangling files.
+ close();
+ fs_.reset(new std::fstream(filename_.c_str(), std::fstream::out));
+ if (!fs_->is_open()) {
+ close();
+ isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
+ }
+ // Opened successfully. Write a header to it.
+ try {
+ CSVRow header(getColumnCount());
+ for (size_t i = 0; i < getColumnCount(); ++i) {
+ header.writeAt(i, getColumnName(i));
+ }
+ *fs_ << header << std::endl;
+
+ } catch (const std::exception& ex) {
+ close();
+ isc_throw(CSVFileError, ex.what());
+ }
+
+}
+
+bool
+CSVFile::validate(const CSVRow& row) {
+ setReadMsg("success");
+ bool ok = (row.getValuesCount() == getColumnCount());
+ if (!ok) {
+ std::ostringstream s;
+ s << "the size of the row '" << row << "' doesn't match the number of"
+ " columns '" << getColumnCount() << "' of the CSV file '"
+ << filename_ << "'";
+ setReadMsg(s.str());
+ }
+ return (ok);
+}
+
+bool
+CSVFile::validateHeader(const CSVRow& header) {
+ if (getColumnCount() == 0) {
+ return (true);
+ }
+
+ if (getColumnCount() != header.getValuesCount()) {
+ return (false);
+ }
+
+ for (size_t i = 0; i < getColumnCount(); ++i) {
+ if (getColumnName(i) != header.readAt(i)) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+const std::string CSVRow::escape_tag("&#x");
+
+std::string
+CSVRow::escapeCharacters(const std::string& orig_str, const std::string& characters) {
+ size_t char_pos = 0;
+ size_t prev_pos = 0;
+
+ // We add the first character of the escape tag to the list of
+ // characters to escape. This ensures input which happens to
+ // be valid escape sequences will be escaped.
+ std::string escape_chars(characters + escape_tag[0]);
+
+ // Check for a first occurrence. If none, just return a
+ // copy of the original.
+ char_pos = orig_str.find_first_of(escape_chars, prev_pos);
+ if (char_pos == std::string::npos) {
+ return(orig_str);
+ }
+
+ std::stringstream ss;
+ while (char_pos < orig_str.size()) {
+ // Copy everything upto the character to escape.
+ ss << orig_str.substr(prev_pos, char_pos - prev_pos);
+
+ // Copy the escape tag followed by the hex digits of the character.
+ ss << escape_tag << std::hex << std::setw(2)
+ << static_cast<uint16_t>(orig_str[char_pos]);
+
+ ++char_pos;
+ prev_pos = char_pos;
+
+ // Find the next character to escape.
+ char_pos = orig_str.find_first_of(escape_chars, prev_pos);
+
+ // If no more, copy the remainder of the string.
+ if (char_pos == std::string::npos) {
+ ss << orig_str.substr(prev_pos, char_pos - prev_pos);
+ break;
+ }
+
+ };
+
+ // Return the escaped string.
+ return(ss.str());
+}
+
+std::string
+CSVRow::unescapeCharacters(const std::string& escaped_str) {
+ size_t esc_pos = 0;
+ size_t start_pos = 0;
+
+ // Look for the escape tag.
+ esc_pos = escaped_str.find(escape_tag, start_pos);
+ if (esc_pos == std::string::npos) {
+ // No escape tags at all, we're done.
+ return(escaped_str);
+ }
+
+ // We have at least one escape tag.
+ std::stringstream ss;
+ while (esc_pos < escaped_str.size()) {
+ // Save everything up to the tag.
+ ss << escaped_str.substr(start_pos, esc_pos - start_pos);
+
+ // Now we need to see if we have valid hex digits
+ // following the tag.
+ unsigned int escaped_char = 0;
+ bool converted = true;
+ size_t dig_pos = esc_pos + escape_tag.size();
+ if (dig_pos <= escaped_str.size() - 2) {
+ for (int i = 0; i < 2; ++i) {
+ uint8_t digit = escaped_str[dig_pos];
+
+ if (digit >= 'a' && digit <= 'f') {
+ digit = digit - 'a' + 10;
+ } else if (digit >= 'A' && digit <= 'F') {
+ digit = digit - 'A' + 10;
+ } else if (digit >= '0' && digit <= '9') {
+ digit -= '0';
+ } else {
+ converted = false;
+ break;
+ }
+
+ if (i == 0) {
+ escaped_char = digit << 4;
+ } else {
+ escaped_char |= digit;
+ }
+
+ ++dig_pos;
+ }
+ }
+
+ // If we converted an escaped character, add it.
+ if (converted) {
+ ss << static_cast<unsigned char>(escaped_char);
+ esc_pos = dig_pos;
+ } else {
+ // Apparently the escape_tag was not followed by two valid hex
+ // digits. We'll assume it just happens to be in the string, so
+ // we'll include it in the output.
+ ss << escape_tag;
+ esc_pos += escape_tag.size();
+ }
+
+ // Set the new start of search.
+ start_pos = esc_pos;
+
+ // Look for the next escape tag.
+ esc_pos = escaped_str.find(escape_tag, start_pos);
+
+ // If we're at the end we're done.
+ if (esc_pos == std::string::npos) {
+ // Make sure we grab the remnant.
+ ss << escaped_str.substr(start_pos, esc_pos - start_pos);
+ break;
+ }
+ };
+
+ return(ss.str());
+}
+
+
+} // end of isc::util namespace
+} // end of isc namespace
diff --git a/src/lib/util/csv_file.h b/src/lib/util/csv_file.h
new file mode 100644
index 0000000..65b62ba
--- /dev/null
+++ b/src/lib/util/csv_file.h
@@ -0,0 +1,575 @@
+// Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CSV_FILE_H
+#define CSV_FILE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <fstream>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown when an error occurs during CSV file processing.
+class CSVFileError : public Exception {
+public:
+ CSVFileError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a single row of the CSV file.
+///
+/// The object of this type can create the string holding a collection of the
+/// comma separated values, representing a row of the CSV file. It allows the
+/// selection of any character as a separator for the values. The default
+/// separator is the comma symbol.
+///
+/// The @c CSVRow object can be constructed in two different ways. The first
+/// option is that the caller creates an object holding empty values
+/// and then adds values one by one. Note that it is possible to either add
+/// a string or a number. The number is converted to the appropriate text
+/// representation. When all the values are added, the text representation of
+/// the row can be obtained by calling @c CSVRow::render function or output
+/// stream operator.
+///
+/// The @c CSVRow object can be also constructed by parsing a row of a CSV
+/// file. In this case, the separator has to be known in advance and passed to
+/// the class constructor. The constructor will call the @c CSVRow::parse
+/// function internally to tokenize the CSV row and create the collection of
+/// values. The class accessors can be then used to retrieve individual values.
+///
+/// This class is meant to be used by the @c CSVFile class to manipulate
+/// individual rows of the CSV file.
+class CSVRow {
+public:
+
+ /// @brief Constructor, creates the raw to be used for output.
+ ///
+ /// Creates CSV row with empty values. The values should be
+ /// later set using the @c CSVRow::writeAt functions. When the
+ /// @c CSVRow::render is called, the text representation of the
+ /// row will be created using a separator character specified
+ /// as an argument of this constructor.
+ ///
+ /// This constructor is exception-free.
+ ///
+ /// @param cols Number of values in the row.
+ /// @param separator Character used as a separator between values in the
+ /// text representation of the row.
+ CSVRow(const size_t cols = 0, const char separator = ',');
+
+ /// @brief Constructor, parses a single row of the CSV file.
+ ///
+ /// This constructor should be used to parse a single row of the CSV
+ /// file. The separator being used for the particular row needs to
+ /// be known in advance and specified as an argument of the constructor
+ /// if other than the default separator is used in the row being parsed.
+ /// An example string to be parsed by this function looks as follows:
+ /// "foo,bar,foo-bar".
+ ///
+ /// This constructor is exception-free.
+ ///
+ /// @param text Text representation of the CSV row.
+ /// @param separator Character being used as a separator in a parsed file.
+ CSVRow(const std::string& text, const char separator = ',');
+
+ /// @brief Returns number of values in a CSV row.
+ size_t getValuesCount() const {
+ return (values_.size());
+ }
+
+ /// @brief Parse the CSV file row.
+ ///
+ /// This function parses a string containing CSV values and assigns them
+ /// to the @c values_ private container. These values can be retrieved
+ /// from the container by calling @c CSVRow::readAt function.
+ ///
+ /// This function is exception-free.
+ ///
+ /// @param line String holding a row of comma separated values.
+ void parse(const std::string& line);
+
+ /// @brief Retrieves a value from the internal container.
+ ///
+ /// @param at Index of the value in the container. The values are indexed
+ /// from 0, where 0 corresponds to the left-most value in the CSV file row.
+ ///
+ /// @return Value at specified index in the text form.
+ ///
+ /// @throw CSVFileError if the index is out of range. The number of elements
+ /// being held by the container can be obtained using
+ /// @c CSVRow::getValuesCount.
+ std::string readAt(const size_t at) const;
+
+ /// @brief Retrieves a value from the internal container, free of escaped
+ /// characters.
+ ///
+ /// Returns a copy of the internal container value at the given index
+ /// which has had all escaped characters replaced with their unescaped
+ /// values. Escaped characters embedded using the following format:
+ ///
+ /// This function fetches the value at the given index and passes it
+ /// into CSVRow::unescapeCharacters which replaces any escaped special
+ /// characters with their unescaped form.
+ ///
+ /// @param at Index of the value in the container. The values are indexed
+ /// from 0, where 0 corresponds to the left-most value in the CSV file row.
+ ///
+ /// @return Value at specified index in the text form.
+ ///
+ /// @throw CSVFileError if the index is out of range. The number of elements
+ /// being held by the container can be obtained using
+ /// @c CSVRow::getValuesCount.
+ std::string readAtEscaped(const size_t at) const;
+
+ /// @brief Trims a given number of elements from the end of a row
+ ///
+ /// @param count number of elements to trim
+ ///
+ /// @throw CSVFileError if the number to trim is larger than
+ /// then the number of elements
+ void trim(const size_t count);
+
+ /// @brief Retrieves a value from the internal container.
+ ///
+ /// This method is reads a value from the internal container and converts
+ /// this value to the type specified as a template parameter. Internally
+ /// it uses @c boost::lexical_cast.
+ ///
+ /// @param at Index of the value in the container. The values are indexed
+ /// from 0, where 0 corresponds to the left-most value in the CSV file row.
+ /// @tparam T type of the value to convert to.
+ ///
+ /// @return Converted value.
+ ///
+ /// @throw CSVFileError if the index is out of range or if the
+ /// @c boost::bad_lexical_cast is thrown by the @c boost::lexical_cast.
+ template<typename T>
+ T readAndConvertAt(const size_t at) const {
+ T cast_value;
+ try {
+ cast_value = boost::lexical_cast<T>(readAt(at).c_str());
+
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(CSVFileError, ex.what());
+ }
+ return (cast_value);
+ }
+
+ /// @brief Creates a text representation of the CSV file row.
+ ///
+ /// This function iterates over all values currently held in the internal
+ /// @c values_ container and appends them to a string. The values are
+ /// separated using the separator character specified in the constructor.
+ ///
+ /// This function is exception free.
+ ///
+ /// @return Text representation of the CSV file row.
+ std::string render() const;
+
+ /// @brief Replaces the value at specified index.
+ ///
+ /// This function is used to set values to be rendered using
+ /// @c CSVRow::render function.
+ ///
+ /// @param at Index of the value to be replaced.
+ /// @param value Value to be written given as string.
+ ///
+ /// @throw CSVFileError if index is out of range.
+ void writeAt(const size_t at, const char* value);
+
+ /// @brief Replaces the value at specified index.
+ ///
+ /// This function is used to set values to be rendered using
+ /// @c CSVRow::render function.
+ ///
+ /// @param at Index of the value to be replaced.
+ /// @param value Value to be written given as string.
+ ///
+ /// @throw CSVFileError if index is out of range.
+ void writeAt(const size_t at, const std::string& value) {
+ writeAt(at, value.c_str());
+ }
+
+ /// @brief Replaces the value at the specified index with a value that has
+ /// had special characters escaped
+ ///
+ /// This function first calls @c CSVRow::escapeCharacters to replace
+ /// special characters with their escaped form. It then sets the value
+ /// to be rendered using @c CSVRow::render function.
+ ///
+ /// @param at Index of the value to be replaced.
+ /// @param value Value to be written given as string.
+ ///
+ /// @throw CSVFileError if index is out of range.
+ void writeAtEscaped(const size_t at, const std::string& value);
+
+ /// @brief Appends the value as a new column.
+ ///
+ /// @param value Value to be written.
+ /// @tparam T Type of the value being written.
+ template<typename T>
+ void append(const T value) {
+ try {
+ values_.push_back(boost::lexical_cast<std::string>(value));
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(CSVFileError, "unable to stringify the value to be "
+ "appended to the CSV file row.");
+ }
+ }
+
+ /// @brief Replaces the value at specified index.
+ ///
+ /// This function is used to set values to be rendered using
+ /// @c CSVRow::render function.
+ ///
+ /// @param at Index of the value to be replaced.
+ /// @param value Value to be written - typically a number.
+ /// @tparam T Type of the value being written.
+ ///
+ /// @throw CSVFileError if index is out of range.
+ template<typename T>
+ void writeAt(const size_t at, const T value) {
+ checkIndex(at);
+ try {
+ values_[at] = boost::lexical_cast<std::string>(value);
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(CSVFileError, "unable to stringify the value to be"
+ " written in the CSV file row at position '"
+ << at << "'");
+ }
+ }
+
+ /// @brief Equality operator.
+ ///
+ /// Two CSV rows are equal when their string representation is equal. This
+ /// includes the order of fields, separator etc.
+ ///
+ /// @param other Object to compare to.
+ bool operator==(const CSVRow& other) const {
+ return (render() == other.render());
+ }
+
+ /// @brief Unequality operator.
+ ///
+ /// Two CSV rows are unequal when their string representation is unequal.
+ /// This includes the order of fields, separator etc.
+ ///
+ /// @param other Object to compare to.
+ bool operator!=(const CSVRow& other) const {
+ return (render() != other.render());
+ }
+
+ /// @brief Returns a copy of a string with special characters escaped
+ ///
+ /// @param orig_str string which may contain characters that require
+ /// escaping.
+ /// @param characters list of characters which require escaping.
+ ///
+ /// The escaped characters will use the following format:
+ ///
+ /// @verbatim
+ /// &#x{xx}
+ /// @endverbatim
+ ///
+ /// where {xx} is the two digit hexadecimal ASCII value of the character
+ /// escaped. A comma, for example is:
+ ///
+ /// &\#x2c
+ ///
+ /// @return A copy of the original string with special characters escaped.
+ static std::string escapeCharacters(const std::string& orig_str,
+ const std::string& characters);
+
+ /// @brief Returns a copy of a string with special characters unescaped
+ ///
+ /// This function reverses the escaping of characters done by @c
+ /// CSVRow::escapeCharacters.
+ ///
+ /// @param escaped_str string which may contain escaped characters.
+ ///
+ /// @return A string free of escaped characters
+ static std::string unescapeCharacters(const std::string& escaped_str);
+
+private:
+
+ /// @brief Check if the specified index of the value is in range.
+ ///
+ /// This function is used internally by other functions.
+ ///
+ /// @param at Value index.
+ /// @throw CSVFileError if specified index is not in range.
+ void checkIndex(const size_t at) const;
+
+ /// @brief Separator character specified in the constructor.
+ ///
+ /// @note Separator is held as a string object (one character long),
+ /// because the boost::is_any_of algorithm requires a string, not a
+ /// char value. If we held the separator as a char, we would need to
+ /// convert it to string on every call to @c CSVRow::parse.
+ std::string separator_;
+
+ /// @brief Internal container holding values that belong to the row.
+ std::vector<std::string> values_;
+
+ /// @brief Prefix used to escape special characters.
+ static const std::string escape_tag;
+};
+
+/// @brief Overrides standard output stream operator for @c CSVRow object.
+///
+/// The resulting string of characters is the same as the one returned by
+/// @c CSVRow::render function.
+///
+/// @param os Output stream.
+/// @param row Object representing a CSV file row.
+std::ostream& operator<<(std::ostream& os, const CSVRow& row);
+
+/// @brief Provides input/output access to CSV files.
+///
+/// This class provides basic methods to access (parse) and create CSV files.
+/// The file is identified by its name qualified with the absolute path.
+/// The name of the file is passed to the constructor. Constructor doesn't
+/// open/create a file, but simply records a file name specified by a caller.
+///
+/// There are two functions that can be used to open a file:
+/// - @c open - opens an existing file; if the file doesn't exist it creates it,
+/// - @c recreate - removes existing file and creates a new one.
+///
+/// When the file is opened its header file is parsed and column names are
+/// identified. At this point it is already possible to get the list of the
+/// column names using appropriate accessors. The data rows are not parsed
+/// at this time. The row parsing is triggered by calling @c next function.
+/// The result of parsing a row is stored in the @c CSVRow object passed as
+/// a parameter.
+///
+/// When the new file is created (when @c recreate is called), the CSV header is
+/// immediately written into it. The header consists of the column names
+/// specified with the @c addColumn function. The subsequent rows are written
+/// into this file by calling @c append.
+class CSVFile {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param filename CSV file name.
+ CSVFile(const std::string& filename);
+
+ /// @brief Destructor
+ virtual ~CSVFile();
+
+ /// @brief Adds new column name.
+ ///
+ /// This column adds a new column but doesn't write it to the file yet.
+ /// The name of the column will be placed in the CSV header when new file
+ /// is created by calling @c recreate or @c open function.
+ ///
+ /// @param col_name Name of the column.
+ ///
+ /// @throw CSVFileError if a column with the specified name exists.
+ void addColumn(const std::string& col_name);
+
+ /// @brief Writes the CSV row into the file.
+ ///
+ /// @param row Object representing a CSV file row.
+ ///
+ /// @throw CSVFileError When error occurred during IO operation or if the
+ /// size of the row doesn't match the number of columns.
+ void append(const CSVRow& row) const;
+
+ /// @brief Closes the CSV file.
+ void close();
+
+ /// @brief Checks if the CSV file exists and can be opened for reading.
+ ///
+ /// This method doesn't check if the existing file has a correct file
+ /// format.
+ ///
+ /// @return true if file exists, false otherwise.
+ bool exists() const;
+
+ /// @brief Flushes a file.
+ void flush() const;
+
+ /// @brief Returns the number of columns in the file.
+ size_t getColumnCount() const {
+ return (cols_.size());
+ }
+
+ /// @brief Returns the path to the CSV file.
+ std::string getFilename() const {
+ return (filename_);
+ }
+
+ /// @brief Returns the description of the last error returned by the
+ /// @c CSVFile::next function.
+ ///
+ /// @return Description of the last error during row validation.
+ std::string getReadMsg() const {
+ return (read_msg_);
+ }
+
+ /// @brief Returns the index of the column having specified name.
+ ///
+ /// This function is exception safe.
+ ///
+ /// @param col_name Name of the column.
+ /// @return Index of the column.
+ /// @throw OutOfRange if column with such name doesn't exist.
+ size_t getColumnIndex(const std::string& col_name) const;
+
+ /// @brief Returns the name of the column.
+ ///
+ /// @param col_index Index of the column.
+ ///
+ /// @return Name of the column.
+ /// @throw CSVFileError if the specified index is out of range.
+ std::string getColumnName(const size_t col_index) const;
+
+ /// @brief Reads next row from CSV file.
+ ///
+ /// This function will return the @c CSVRow object representing a
+ /// parsed row if parsing is successful. If the end of file has been
+ /// reached, the empty row is returned (a row containing no values).
+ ///
+ /// @param [out] row Object receiving the parsed CSV file.
+ /// @param skip_validation Do not perform validation.
+ ///
+ /// @return true if row has been read and validated; false if validation
+ /// failed.
+ bool next(CSVRow& row, const bool skip_validation = false);
+
+ /// @brief Opens existing file or creates a new one.
+ ///
+ /// This function will try to open existing file if this file has size
+ /// greater than 0. If the file doesn't exist or has size of 0, the
+ /// file is recreated. If the existing file has been opened, the header
+ /// is parsed and column names are initialized in the @c CSVFile object.
+ /// By default, the data pointer in the file is set to the beginning of
+ /// the first row. In order to retrieve the row contents the @c next
+ /// function should be called. If a @c seek_to_end parameter is set to
+ /// true, the file will be opened and the internal pointer will be set
+ /// to the end of file.
+ ///
+ /// @param seek_to_end A boolean value which indicates if the input and
+ /// output file pointer should be set at the end of file.
+ ///
+ /// @throw CSVFileError when IO operation fails.
+
+ virtual void open(const bool seek_to_end = false);
+
+ /// @brief Creates a new CSV file.
+ ///
+ /// The file creation will fail if there are no columns specified.
+ /// Otherwise, this function will write the header to the file.
+ /// In order to write rows to opened file, the @c append function
+ /// should be called.
+ virtual void recreate();
+
+ /// @brief Sets error message after row validation.
+ ///
+ /// The @c CSVFile::validate function is responsible for setting the
+ /// error message after validation of the row read from the CSV file.
+ /// It will use this function to set this message. Note, that the
+ /// @c validate function can set a message after successful validation
+ /// too. Such message could say "success", or something similar.
+ ///
+ /// @param read_msg Error message to be set.
+ void setReadMsg(const std::string& read_msg) {
+ read_msg_ = read_msg;
+ }
+
+ /// @brief Represents empty row.
+ static CSVRow EMPTY_ROW() {
+ static CSVRow row(0);
+ return (row);
+ }
+
+protected:
+
+ /// @brief Adds a column regardless if the file is open or not.
+ ///
+ /// This function adds as new column to the collection. It is meant to be
+ /// called internally by the methods of the base class and derived classes.
+ /// It must not be used in the public scope. The @c CSVFile::addColumn
+ /// must be used in the public scope instead, because it prevents addition
+ /// of the new column when the file is open.
+ ///
+ /// @param col_name Name of the column.
+ ///
+ /// @throw CSVFileError if a column with the specified name exists.
+ void addColumnInternal(const std::string& col_name);
+
+ /// @brief Validate the row read from a file.
+ ///
+ /// This function implements a basic validation for the row read from the
+ /// CSV file. It is virtual so as it may be customized in derived classes.
+ ///
+ /// This default implementation checks that the number of values in the
+ /// row corresponds to the number of columns specified for this file.
+ ///
+ /// If row validation fails, the error message is noted and can be retrieved
+ /// using @c CSVFile::getReadMsg. The function which overrides this
+ /// base implementation is responsible for setting the error message using
+ /// @c CSVFile::setReadMsg.
+ ///
+ /// @param row A row to be validated.
+ ///
+ /// @return true if the column is valid; false otherwise.
+ virtual bool validate(const CSVRow& row);
+
+protected:
+
+ /// @brief This function validates the header of the CSV file.
+ ///
+ /// If there are any columns added to the @c CSVFile object, it will
+ /// compare that they exactly match (including order) the header read
+ /// from the file.
+ ///
+ /// This function is called internally by @ref CSVFile::open. Derived classes
+ /// may add extra validation steps.
+ ///
+ /// @param header A row holding a header.
+ /// @return true if header matches the columns; false otherwise.
+ virtual bool validateHeader(const CSVRow& header);
+
+private:
+ /// @brief Sanity check if stream is open.
+ ///
+ /// Checks if the file stream is open so as IO operations can be performed
+ /// on it. This is internally called by the public class members to prevent
+ /// them from performing IO operations on invalid stream and using NULL
+ /// pointer to a stream. The @c clear() method is called on the stream
+ /// after the status has been checked.
+ ///
+ /// @throw CSVFileError if stream is closed or pointer to it is NULL.
+ void checkStreamStatusAndReset(const std::string& operation) const;
+
+ /// @brief Returns size of the CSV file.
+ std::streampos size() const;
+
+ /// @brief CSV file name.
+ std::string filename_;
+
+ /// @brief Holds a pointer to the file stream.
+ boost::shared_ptr<std::fstream> fs_;
+
+ /// @brief Holds CSV file columns.
+ std::vector<std::string> cols_;
+
+ /// @brief Holds last error during row reading or validation.
+ std::string read_msg_;
+};
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // CSV_FILE_H
diff --git a/src/lib/util/dhcp_space.cc b/src/lib/util/dhcp_space.cc
new file mode 100644
index 0000000..46bb3b1
--- /dev/null
+++ b/src/lib/util/dhcp_space.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/dhcp_space.h>
+
+namespace isc {
+namespace util {
+
+template <>
+char const* cStringDhcpSpace<DHCPv4>() {
+ return "4";
+}
+
+template <>
+char const* cStringDhcpSpace<DHCPv6>() {
+ return "6";
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/dhcp_space.h b/src/lib/util/dhcp_space.h
new file mode 100644
index 0000000..e34c7cb
--- /dev/null
+++ b/src/lib/util/dhcp_space.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef ISC_UTIL_DHCP_SPACE_H
+#define ISC_UTIL_DHCP_SPACE_H 1
+
+#include <string>
+
+#include <boost/algorithm/string/replace.hpp>
+
+namespace isc {
+namespace util {
+
+enum DhcpSpace {
+ DHCPv4,
+ DHCPv6,
+};
+
+/// @brief Provides the C string representation of the DHCP space.
+///
+/// @tparam D DHCP space
+///
+/// @return "4" or "6"
+template <DhcpSpace D>
+char const* cStringDhcpSpace();
+
+/// @brief Replaces all occurrences of {} with 4 or 6 based on the templated DHCP space.
+///
+/// @tparam D DHCP space
+///
+/// @return the formatted string
+template <DhcpSpace D>
+std::string formatDhcpSpace(char const* const format_string) {
+ std::string result(format_string);
+ boost::replace_all(result, "{}", cStringDhcpSpace<D>());
+ return result;
+}
+
+} // namespace util
+} // namespace isc
+
+#endif // ISC_UTIL_DHCP_SPACE_H
diff --git a/src/lib/util/doubles.h b/src/lib/util/doubles.h
new file mode 100644
index 0000000..780b8fc
--- /dev/null
+++ b/src/lib/util/doubles.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef DOUBLES_H
+#define DOUBLES_H
+
+#include <complex>
+
+namespace isc {
+namespace util {
+
+/// @brief Tests two doubles for equivalence within a given tolerance.
+///
+/// @param a comparison operand one
+/// @param b comparison operand two
+/// @param tolerance the amount by which the two values may differ and
+/// still be considered "equal".
+/// @return True if the two values differ by less than the tolerance.
+inline bool areDoublesEquivalent(double a, double b, double tolerance=0.000001) {
+ return(std::abs(a - b) < tolerance);
+}
+
+} // namespace util
+} // namespace isc
+
+#endif // DOUBLES_H
diff --git a/src/lib/util/encode/base16_from_binary.h b/src/lib/util/encode/base16_from_binary.h
new file mode 100644
index 0000000..3eb697d
--- /dev/null
+++ b/src/lib/util/encode/base16_from_binary.h
@@ -0,0 +1,103 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BASE16_FROM_BINARY_HPP
+#define BOOST_ARCHIVE_ITERATORS_BASE16_FROM_BINARY_HPP
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// base16_from_binary.h (derived from boost base64_from_binary.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+// See http://www.boost.org for updates, documentation, and revision history.
+
+#include <exceptions/isc_assert.h>
+
+#include <cstddef> // size_t
+#include <boost/config.hpp> // for BOOST_DEDUCED_TYPENAME
+#if defined(BOOST_NO_STDC_NAMESPACE)
+namespace std{
+ using ::size_t;
+} // namespace std
+#endif
+
+// See base32hex_from_binary.h for why we need base64_from...hpp here.
+#include <boost/archive/iterators/base64_from_binary.hpp>
+
+namespace boost {
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert binary integers to base16 characters
+
+namespace detail {
+
+template<class CharType>
+struct from_4_bit {
+ typedef CharType result_type;
+ CharType operator()(CharType t) const{
+ const char * lookup_table =
+ "0123456789"
+ "ABCDEF";
+ isc_throw_assert(t < 16);
+ return (lookup_table[static_cast<size_t>(t)]);
+ }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+// typedef transform_iterator<
+// from_4_bit<CharType>,
+// transform_width<Base, 4, sizeof(Base::value_type) * 8, CharType>
+// > base16_from_binary;
+// but C++ won't accept this. Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor. This makes it incompatible with the dataflow
+// ideal. This is also addressed here.
+
+//template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+template<
+ class Base,
+ class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class base16_from_binary :
+ public transform_iterator<
+ detail::from_4_bit<CharType>,
+ Base
+ >
+{
+ friend class boost::iterator_core_access;
+ typedef transform_iterator<
+ BOOST_DEDUCED_TYPENAME detail::from_4_bit<CharType>,
+ Base
+ > super_t;
+
+public:
+ // make composable by using templated constructor
+ template<class T>
+ base16_from_binary(T start) :
+ super_t(
+ Base(static_cast<T>(start)),
+ detail::from_4_bit<CharType>()
+ )
+ {}
+ // intel 7.1 doesn't like default copy constructor
+ base16_from_binary(const base16_from_binary & rhs) :
+ super_t(
+ Base(rhs.base_reference()),
+ detail::from_4_bit<CharType>()
+ )
+ {}
+// base16_from_binary(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BASE16_FROM_BINARY_HPP
diff --git a/src/lib/util/encode/base32hex.h b/src/lib/util/encode/base32hex.h
new file mode 100644
index 0000000..0a85b36
--- /dev/null
+++ b/src/lib/util/encode/base32hex.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BASE32HEX_H
+#define BASE32HEX_H 1
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+//
+// Note: this helper module isn't specific to the DNS protocol per se.
+// We should probably move this to somewhere else, possibly in some common
+// utility area.
+//
+
+namespace isc {
+namespace util {
+namespace encode {
+
+/// \brief Encode binary data in the base32hex format.
+///
+/// The underlying implementation is shared with \c encodeBase64, and all
+/// description except the format (base32hex) equally applies.
+///
+/// Note: the encoding format is base32hex, not base32.
+///
+/// \param binary A vector object storing the data to be encoded.
+/// \return A newly created string that stores base32hex encoded value for
+/// binary.
+std::string encodeBase32Hex(const std::vector<uint8_t>& binary);
+
+/// \brief Decode a text encoded in the base32hex format into the
+/// original %data.
+///
+/// The underlying implementation is shared with \c decodeBase64, and all
+/// description except the format (base32hex) equally applies.
+///
+/// Note: the encoding format is base32hex, not base32.
+///
+/// \param input A text encoded in the base32hex format.
+/// \param result A vector in which the decoded %data is to be stored.
+void decodeBase32Hex(const std::string& input, std::vector<uint8_t>& result);
+
+} // namespace encode
+} // namespace util
+} // namespace isc
+
+#endif // BASE32HEX_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/base32hex_from_binary.h b/src/lib/util/encode/base32hex_from_binary.h
new file mode 100644
index 0000000..84f2b69
--- /dev/null
+++ b/src/lib/util/encode/base32hex_from_binary.h
@@ -0,0 +1,105 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BASE32HEX_FROM_BINARY_HPP
+#define BOOST_ARCHIVE_ITERATORS_BASE32HEX_FROM_BINARY_HPP
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// base32hex_from_binary.h (derived from boost base64_from_binary.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+// See http://www.boost.org for updates, documentation, and revision history.
+
+#include <exceptions/isc_assert.h>
+
+#include <cstddef> // size_t
+#include <boost/config.hpp> // for BOOST_DEDUCED_TYPENAME
+#if defined(BOOST_NO_STDC_NAMESPACE)
+namespace std{
+ using ::size_t;
+} // namespace std
+#endif
+
+// We use the same boost header files used in "base64_from_". Since the
+// precise path to these headers may vary depending on the boost version we
+// simply include the base64 header here.
+#include <boost/archive/iterators/base64_from_binary.hpp>
+
+namespace boost {
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert binary integers to base32hex characters
+
+namespace detail {
+
+template<class CharType>
+struct from_5_bit {
+ typedef CharType result_type;
+ CharType operator()(CharType t) const{
+ const char * lookup_table =
+ "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUV";
+ isc_throw_assert(t < 32);
+ return (lookup_table[static_cast<size_t>(t)]);
+ }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+// typedef transform_iterator<
+// from_5_bit<CharType>,
+// transform_width<Base, 5, sizeof(Base::value_type) * 8, CharType>
+// > base32hex_from_binary;
+// but C++ won't accept this. Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor. This makes it incompatible with the dataflow
+// ideal. This is also addressed here.
+
+//template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+template<
+ class Base,
+ class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class base32hex_from_binary :
+ public transform_iterator<
+ detail::from_5_bit<CharType>,
+ Base
+ >
+{
+ friend class boost::iterator_core_access;
+ typedef transform_iterator<
+ BOOST_DEDUCED_TYPENAME detail::from_5_bit<CharType>,
+ Base
+ > super_t;
+
+public:
+ // make composable by using templated constructor
+ template<class T>
+ base32hex_from_binary(T start) :
+ super_t(
+ Base(static_cast<T>(start)),
+ detail::from_5_bit<CharType>()
+ )
+ {}
+ // intel 7.1 doesn't like default copy constructor
+ base32hex_from_binary(const base32hex_from_binary & rhs) :
+ super_t(
+ Base(rhs.base_reference()),
+ detail::from_5_bit<CharType>()
+ )
+ {}
+// base32hex_from_binary(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BASE32HEX_FROM_BINARY_HPP
diff --git a/src/lib/util/encode/base64.h b/src/lib/util/encode/base64.h
new file mode 100644
index 0000000..84280ec
--- /dev/null
+++ b/src/lib/util/encode/base64.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BASE64_H
+#define BASE64_H 1
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+//
+// Note: this helper module isn't specific to the DNS protocol per se.
+// We should probably move this to somewhere else, possibly in some common
+// utility area.
+//
+
+namespace isc {
+namespace util {
+namespace encode {
+
+/// \brief Encode binary data in the base64 format.
+///
+/// This function returns a new \c std::string object that stores a text
+/// encoded in the base64 format for the given \c binary %data.
+/// The resulting string will be a valid, canonical form of base64
+/// representation as specified in RFC4648.
+///
+/// If memory allocation for the returned string fails, a corresponding
+/// standard exception will be thrown. This function never throws exceptions
+/// otherwise.
+///
+/// \param binary A vector object storing the data to be encoded.
+/// \return A newly created string that stores base64 encoded value for binary.
+std::string encodeBase64(const std::vector<uint8_t>& binary);
+
+/// \brief Decode a text encoded in the base64 format into the original %data.
+///
+/// The \c input argument must be a valid string represented in the base64
+/// format as specified in RFC4648. Space characters (spaces, tabs, newlines)
+/// can be included in \c input and will be ignored. Without spaces, the
+/// length of string must be a multiple of 4 bytes with necessary paddings.
+/// Also it must be encoded using the canonical encoding (see RFC4648).
+/// If any of these conditions is not met, an exception of class
+/// \c isc::BadValue will be thrown.
+///
+/// If \c result doesn't have sufficient capacity to store all decoded %data
+/// and memory allocation fails, a corresponding standard exception will be
+/// thrown. If the caller knows the necessary length (which can in theory
+/// be calculated from the input string), this situation can be avoided by
+/// reserving sufficient space for \c result beforehand.
+///
+/// Any existing %data in \c result will be removed. This is the case in some
+/// of the cases where an exception is thrown; that is, this function only
+/// provides the basic exception guarantee.
+///
+/// \param input A text encoded in the base64 format.
+/// \param result A vector in which the decoded %data is to be stored.
+void decodeBase64(const std::string& input, std::vector<uint8_t>& result);
+
+} // namespace encode
+} // namespace util
+} // namespace isc
+
+#endif // BASE64_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/base_n.cc b/src/lib/util/encode/base_n.cc
new file mode 100644
index 0000000..e0c37e5
--- /dev/null
+++ b/src/lib/util/encode/base_n.cc
@@ -0,0 +1,494 @@
+// Copyright (C) 2010-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/encode/base32hex_from_binary.h>
+#include <util/encode/binary_from_base32hex.h>
+#include <util/encode/base16_from_binary.h>
+#include <util/encode/binary_from_base16.h>
+#include <util/encode/base32hex.h>
+#include <util/encode/base64.h>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/binary_from_base64.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
+#ifdef HAVE_BOOST_INTEGER_COMMON_FACTOR_HPP
+#include <boost/integer/common_factor.hpp>
+#else
+#include <boost/math/common_factor.hpp>
+#endif
+
+#include <stdint.h>
+#include <stdexcept>
+#include <iterator>
+#include <string>
+#include <vector>
+
+using namespace std;
+using namespace boost::archive::iterators;
+
+namespace isc {
+namespace util {
+namespace encode {
+
+// Some versions of clang cannot handle exceptions in unnamed namespaces
+// so this exception is defined in an 'internal' namespace
+namespace clang_unnamed_namespace_workaround {
+// An internally caught exception to unify a few possible cases of the same
+// error.
+class IncompleteBaseInput : public std::exception {
+};
+} // end namespace internal
+
+// In the following anonymous namespace, we provide a generic framework
+// to encode/decode baseN format. We use the following tools:
+// - boost base64_from_binary/binary_from_base64: provide mapping table for
+// base64.
+// These classes take another iterator (Base) as a template argument, and
+// their dereference operator (operator*()) first retrieves an input value
+// from Base via Base::operator* and converts the value using their mapping
+// table. The converted value is returned as their own operator*.
+// - base{32hex,16}_from_binary/binary_from_base{32hex,16}: provide mapping
+// table for base32hex and base16. A straightforward variation of their
+// base64 counterparts.
+// - EncodeNormalizer/DecodeNormalizer: supplemental filter handling baseN
+// padding characters (=)
+// - boost transform_width: an iterator framework for handling data stream
+// per bit-group. It takes another iterator (Base) and output/input bit
+// numbers (BitsOut/BitsIn) template arguments. A transform_width object
+// internally maintains a bit stream, which can be retrieved per BitsOut
+// bits via its dereference operator (operator*()). It builds the stream
+// by internally iterating over the Base object via Base::operator++ and
+// Base::operator*, using the least BitsIn bits of the result of
+// Base::operator*. In our usage BitsIn for encoding and BitsOut for
+// decoding are always 8 (# of bits for one byte).
+//
+// Its dereference operator
+// retrieves BitsIn bits from the result of "*Base" (if necessary it
+// internally calls ++Base)
+//
+// A conceptual description of how the encoding and decoding work is as
+// follows:
+// Encoding:
+// input binary data => Normalizer (append sufficient number of 0 bits)
+// => transform_width (extract bit groups from the original
+// stream)
+// => baseXX_from_binary (convert each bit group to an
+// encoded byte using the mapping)
+// Decoding:
+// input baseXX text => Normalizer (convert '='s to the encoded characters
+// corresponding to 0, e.g. 'A's in base64)
+// => binary_from_baseXX (convert each encoded byte into
+// the original group bit)
+// => transform_width (build original byte stream by
+// concatenating the decoded bit
+// stream)
+//
+// Below, we define a set of templated classes to handle different parameters
+// for different encoding algorithms.
+namespace {
+// Common constants used for all baseN encoding.
+const char BASE_PADDING_CHAR = '=';
+const uint8_t BINARY_ZERO_CODE = 0;
+
+// EncodeNormalizer is an input iterator intended to be used as a filter
+// between the binary stream and baseXX_from_binary translator (via
+// transform_width). An EncodeNormalizer object is configured with two
+// iterators (base and base_end), specifying the head and end of the input
+// stream. It internally iterators over the original stream, and return
+// each byte of the stream intact via its dereference operator until it
+// reaches the end of the stream. After that the EncodeNormalizer object
+// will return 0 no matter how many times it is subsequently incremented.
+// This is necessary because the input binary stream may not contain
+// sufficient bits for a full encoded text while baseXX_from_binary expects
+// a sufficient length of input.
+// Note: this class is intended to be used within this implementation file,
+// and assumes "base < base_end" on construction without validating the
+// arguments. The behavior is undefined if this assumption doesn't hold.
+class EncodeNormalizer {
+public:
+ // Aliases used to enable iterator behavior on this class
+ using iterator_category = input_iterator_tag;
+ using value_type = uint8_t;
+ using difference_type = ptrdiff_t;
+ using pointer = uint8_t*;
+ using reference = uint8_t&;
+
+ EncodeNormalizer(const vector<uint8_t>::const_iterator& base,
+ const vector<uint8_t>::const_iterator& base_end) :
+ base_(base), base_end_(base_end), in_pad_(false)
+ {}
+ EncodeNormalizer& operator++() { // prefix version
+ increment();
+ return (*this);
+ }
+ EncodeNormalizer operator++(int) { // postfix version
+ const EncodeNormalizer copy = *this;
+ increment();
+ return (copy);
+ }
+ const uint8_t& operator*() const {
+ if (in_pad_) {
+ return (BINARY_ZERO_CODE);
+ } else {
+ return (*base_);
+ }
+ }
+ bool operator==(const EncodeNormalizer& other) const {
+ return (base_ == other.base_);
+ }
+private:
+ void increment() {
+ if (!in_pad_) {
+ ++base_;
+ }
+ if (base_ == base_end_) {
+ in_pad_ = true;
+ }
+ }
+ vector<uint8_t>::const_iterator base_;
+ const vector<uint8_t>::const_iterator base_end_;
+ bool in_pad_;
+};
+
+// DecodeNormalizer is an input iterator intended to be used as a filter
+// between the encoded baseX stream and binary_from_baseXX.
+// A DecodeNormalizer object is configured with three string iterators
+// (base, base_beginpad, and base_end), specifying the head of the string,
+// the beginning position of baseX padding (when there's padding), and
+// end of the string, respectively. It internally iterators over the original
+// stream, and return each character of the encoded string via its dereference
+// operator until it reaches base_beginpad. After that the DecodeNormalizer
+// will return the encoding character corresponding to the all-0 value
+// (which is specified on construction via base_zero_code. see also
+// BaseZeroCode below). This translation is necessary because
+// binary_from_baseXX doesn't accept the padding character (i.e. '=').
+// Note: this class is intended to be used within this implementation file,
+// and for simplicity assumes "base < base_beginpad <= base_end" on
+// construction without validating the arguments. The behavior is undefined
+// if this assumption doesn't hold.
+class DecodeNormalizer {
+public:
+ // Aliases used to enable iterator behavior on this class
+ using iterator_category = input_iterator_tag;
+ using value_type = char;
+ using difference_type = ptrdiff_t;
+ using pointer = char*;
+ using reference = char&;
+
+ DecodeNormalizer(const char base_zero_code,
+ const string::const_iterator& base,
+ const string::const_iterator& base_beginpad,
+ const string::const_iterator& base_end,
+ size_t* char_count) :
+ base_zero_code_(base_zero_code),
+ base_(base), base_beginpad_(base_beginpad), base_end_(base_end),
+ in_pad_(false), char_count_(char_count)
+ {
+ // Skip beginning spaces, if any. We need do it here because
+ // otherwise the first call to operator*() would be confused.
+ skipSpaces();
+ }
+ DecodeNormalizer& operator++() {
+ if (base_ < base_end_) {
+ ++*char_count_;
+ }
+ ++base_;
+ skipSpaces();
+ if (base_ == base_beginpad_) {
+ in_pad_ = true;
+ }
+ return (*this);
+ }
+ void skipSpaces() {
+ // If (char is signed and) *base_ < 0, on Windows platform with Visual
+ // Studio compiler it may trigger _ASSERTE((unsigned)(c + 1) <= 256);
+ // so make sure that the parameter of isspace() is larger than 0.
+ // We don't simply cast it to unsigned char to avoid confusing the
+ // isspace() implementation with a possible extension for values
+ // larger than 127. Also note the check is not ">= 0"; for systems
+ // where char is unsigned that would always be true and would possibly
+ // trigger a compiler warning that could stop the build.
+ while (base_ != base_end_ && *base_ > 0 && isspace(*base_)) {
+ ++base_;
+ }
+ }
+ const char& operator*() const {
+ if (base_ == base_end_) {
+ // binary_from_baseX can call this operator when it needs more bits
+ // even if the internal iterator (base_) has reached its end
+ // (if that happens it means the input is an incomplete baseX
+ // string and should be rejected). So this is the only point
+ // we can catch and reject this type of invalid input.
+ //
+ // More recent versions of Boost fixed the behavior and the
+ // out-of-range call to this operator doesn't happen. It's good,
+ // but in that case we need to catch incomplete baseX input in
+ // a different way. It's done via char_count_ and after the
+ // completion of decoding.
+
+ // throw this now and convert it
+ throw clang_unnamed_namespace_workaround::IncompleteBaseInput();
+ }
+ if (*base_ == BASE_PADDING_CHAR) {
+ // Padding can only happen at the end of the input string. We can
+ // detect any violation of this by checking in_pad_, which is
+ // true iff we are on or after the first valid sequence of padding
+ // characters.
+ if (in_pad_) {
+ return (base_zero_code_);
+ } else {
+ isc_throw(BadValue, "Intermediate padding found");
+ }
+ } else {
+ return (*base_);
+ }
+ }
+ bool operator==(const DecodeNormalizer& other) const {
+ return (base_ == other.base_);
+ }
+private:
+ const char base_zero_code_;
+ string::const_iterator base_;
+ const string::const_iterator base_beginpad_;
+ const string::const_iterator base_end_;
+ bool in_pad_;
+ // Store number of non-space decoded characters (incl. pad) here. Define
+ // it as a pointer so we can carry it over to any copied objects.
+ size_t* char_count_;
+};
+
+// BitsPerChunk: number of bits to be converted using the baseN mapping table.
+// e.g. 6 for base64.
+// BaseZeroCode: the byte character that represents a value of 0 in
+// the corresponding encoding. e.g. 'A' for base64.
+// Encoder: baseX_from_binary<transform_width<EncodeNormalizer,
+// BitsPerChunk, 8> >
+// Decoder: transform_width<binary_from_baseX<DecodeNormalizer>,
+// 8, BitsPerChunk>
+template <int BitsPerChunk, char BaseZeroCode,
+ typename Encoder, typename Decoder>
+struct BaseNTransformer {
+ static string encode(const vector<uint8_t>& binary);
+ static void decode(const char* algorithm,
+ const string& base64, vector<uint8_t>& result);
+
+ // BITS_PER_GROUP is the number of bits for the smallest possible (non
+ // empty) bit string that can be converted to a valid baseN encoded text
+ // without padding. It's the least common multiple of 8 and BitsPerChunk,
+ // e.g. 24 for base64.
+ static const int BITS_PER_GROUP =
+#ifdef HAVE_BOOST_INTEGER_COMMON_FACTOR_HPP
+ boost::integer::static_lcm<BitsPerChunk, 8>::value;
+#else
+ boost::math::static_lcm<BitsPerChunk, 8>::value;
+#endif
+
+ // MAX_PADDING_CHARS is the maximum number of padding characters
+ // that can appear in a valid baseN encoded text.
+ // It's group_len - chars_for_byte, where group_len is the number of
+ // encoded characters to represent BITS_PER_GROUP bits, and
+ // chars_for_byte is the number of encoded character that is needed to
+ // represent a single byte, which is ceil(8 / BitsPerChunk).
+ // For example, for base64 we need two encoded characters to represent a
+ // byte, and each group consists of 4 encoded characters, so
+ // MAX_PADDING_CHARS is 4 - 2 = 2.
+ static const int MAX_PADDING_CHARS =
+ BITS_PER_GROUP / BitsPerChunk -
+ (8 / BitsPerChunk + ((8 % BitsPerChunk) == 0 ? 0 : 1));
+};
+
+template <int BitsPerChunk, char BaseZeroCode,
+ typename Encoder, typename Decoder>
+string
+BaseNTransformer<BitsPerChunk, BaseZeroCode, Encoder, Decoder>::encode(
+ const vector<uint8_t>& binary)
+{
+ // calculate the resulting length.
+ size_t bits = binary.size() * 8;
+ if (bits % BITS_PER_GROUP > 0) {
+ bits += (BITS_PER_GROUP - (bits % BITS_PER_GROUP));
+ }
+ const size_t len = bits / BitsPerChunk;
+
+ string result;
+ result.reserve(len);
+ result.assign(Encoder(EncodeNormalizer(binary.begin(), binary.end())),
+ Encoder(EncodeNormalizer(binary.end(), binary.end())));
+ isc_throw_assert(len >= result.length());
+ result.append(len - result.length(), BASE_PADDING_CHAR);
+ return (result);
+}
+
+template <int BitsPerChunk, char BaseZeroCode,
+ typename Encoder, typename Decoder>
+void
+BaseNTransformer<BitsPerChunk, BaseZeroCode, Encoder, Decoder>::decode(
+ const char* const algorithm,
+ const string& input,
+ vector<uint8_t>& result)
+{
+ // enumerate the number of trailing padding characters (=), ignoring
+ // white spaces. since baseN_from_binary doesn't accept padding,
+ // we handle it explicitly.
+ size_t padchars = 0;
+ string::const_reverse_iterator srit = input.rbegin();
+ string::const_reverse_iterator srit_end = input.rend();
+ while (srit != srit_end) {
+ char ch = *srit;
+ if (ch == BASE_PADDING_CHAR) {
+ if (++padchars > MAX_PADDING_CHARS) {
+ isc_throw(BadValue, "Too many " << algorithm
+ << " padding characters: " << input);
+ }
+ } else if (!(ch > 0 && isspace(ch))) {
+ // see the note for DecodeNormalizer::skipSpaces() above for ch > 0
+ break;
+ }
+ ++srit;
+ }
+ // then calculate the number of padding bits corresponding to the padding
+ // characters. In general, the padding bits consist of all-zero
+ // trailing bits of the last encoded character followed by zero bits
+ // represented by the padding characters:
+ // 1st pad 2nd pad 3rd pad...
+ // +++===== ======= ===... (+: from encoded chars, =: from pad chars)
+ // 0000...0 0......0 000...
+ // 0 7 8 15 16.... (bits)
+ // The number of bits for the '==...' part is padchars * BitsPerChunk.
+ // So the total number of padding bits is the smallest multiple of 8
+ // that is >= padchars * BitsPerChunk.
+ // (Below, note the common idiom of the bitwise AND with ~7. It clears the
+ // lowest three bits, so has the effect of rounding the result down to the
+ // nearest multiple of 8)
+ const size_t padbits = (padchars * BitsPerChunk + 7) & ~7;
+
+ // In some encoding algorithm, it could happen that a padding byte would
+ // contain a full set of encoded bits, which is not allowed by definition
+ // of padding. For example, if BitsPerChunk is 5, the following
+ // representation could happen:
+ // ++00000= (+: from encoded chars, 0: encoded char for '0', =: pad chars)
+ // 0 7 (bits)
+ // This must actually be encoded as follows:
+ // ++======
+ // 0 7 (bits)
+ // The following check rejects this type of invalid encoding.
+ if (padbits > BitsPerChunk * (padchars + 1)) {
+ isc_throw(BadValue, "Invalid " << algorithm << " padding: " << input);
+ }
+
+ // convert the number of bits in bytes for convenience.
+ const size_t padbytes = padbits / 8;
+
+ try {
+ size_t char_count = 0;
+ result.assign(Decoder(DecodeNormalizer(BaseZeroCode, input.begin(),
+ srit.base(), input.end(),
+ &char_count)),
+ Decoder(DecodeNormalizer(BaseZeroCode, input.end(),
+ input.end(), input.end(),
+ NULL)));
+
+ // Number of bits of the conversion result including padding must be
+ // a multiple of 8; otherwise the decoder reaches the end of input
+ // with some incomplete bits of data, which is invalid.
+ if (((char_count * BitsPerChunk) % 8) != 0) {
+ // catch this immediately below
+ throw clang_unnamed_namespace_workaround::IncompleteBaseInput();
+ }
+ } catch (const clang_unnamed_namespace_workaround::IncompleteBaseInput&) {
+ // we unify error handling for incomplete input here.
+ isc_throw(BadValue, "Incomplete input for " << algorithm
+ << ": " << input);
+ } catch (const dataflow_exception& ex) {
+ // convert any boost exceptions into our local one.
+ isc_throw(BadValue, ex.what());
+ }
+
+ // Confirm the original BaseX text is the canonical encoding of the
+ // data, that is, that the first byte of padding is indeed 0.
+ // (DecodeNormalizer and binary_from_baseXX ensure that the rest of the
+ // padding is all zero).
+ isc_throw_assert(result.size() >= padbytes);
+ if (padbytes > 0 && *(result.end() - padbytes) != 0) {
+ isc_throw(BadValue, "Non 0 bits included in " << algorithm
+ << " padding: " << input);
+ }
+
+ // strip the padded zero-bit fields
+ result.resize(result.size() - padbytes);
+}
+
+//
+// Instantiation for BASE-64
+//
+typedef
+base64_from_binary<transform_width<EncodeNormalizer, 6, 8> > base64_encoder;
+typedef
+transform_width<binary_from_base64<DecodeNormalizer>, 8, 6> base64_decoder;
+typedef BaseNTransformer<6, 'A', base64_encoder, base64_decoder>
+Base64Transformer;
+
+//
+// Instantiation for BASE-32HEX
+//
+typedef
+base32hex_from_binary<transform_width<EncodeNormalizer, 5, 8> >
+base32hex_encoder;
+typedef
+transform_width<binary_from_base32hex<DecodeNormalizer>, 8, 5>
+base32hex_decoder;
+typedef BaseNTransformer<5, '0', base32hex_encoder, base32hex_decoder>
+Base32HexTransformer;
+
+//
+// Instantiation for BASE-16 (HEX)
+//
+typedef
+base16_from_binary<transform_width<EncodeNormalizer, 4, 8> > base16_encoder;
+typedef
+transform_width<binary_from_base16<DecodeNormalizer>, 8, 4> base16_decoder;
+typedef BaseNTransformer<4, '0', base16_encoder, base16_decoder>
+Base16Transformer;
+}
+
+string
+encodeBase64(const vector<uint8_t>& binary) {
+ return (Base64Transformer::encode(binary));
+}
+
+void
+decodeBase64(const string& input, vector<uint8_t>& result) {
+ Base64Transformer::decode("base64", input, result);
+}
+
+string
+encodeBase32Hex(const vector<uint8_t>& binary) {
+ return (Base32HexTransformer::encode(binary));
+}
+
+void
+decodeBase32Hex(const string& input, vector<uint8_t>& result) {
+ Base32HexTransformer::decode("base32hex", input, result);
+}
+
+string
+encodeHex(const vector<uint8_t>& binary) {
+ return (Base16Transformer::encode(binary));
+}
+
+void
+decodeHex(const string& input, vector<uint8_t>& result) {
+ Base16Transformer::decode("base16", input, result);
+}
+
+} // namespace encode
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/encode/binary_from_base16.h b/src/lib/util/encode/binary_from_base16.h
new file mode 100644
index 0000000..f913dd0
--- /dev/null
+++ b/src/lib/util/encode/binary_from_base16.h
@@ -0,0 +1,112 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE16_HPP
+#define BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE16_HPP
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// binary_from_base16.h (derived from boost binary_from_base64.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+// See http://www.boost.org for updates, documentation, and revision history.
+
+#include <cassert>
+
+// See binary_from_base32hex.h for why we need _from_base64.hpp here.
+#include <boost/archive/iterators/binary_from_base64.hpp>
+
+#include <exceptions/exceptions.h>
+
+namespace boost {
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert base16 characters to binary data
+
+namespace detail {
+
+template<class CharType>
+struct to_4_bit {
+ typedef CharType result_type;
+ CharType operator()(CharType t) const{
+ const signed char lookup_table[] = {
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 00-0f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 10-1f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 20-2f
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, // 30-3f
+ -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 40-4f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 50-5f
+ -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 60-6f
+ };
+ BOOST_STATIC_ASSERT(0x70 == sizeof(lookup_table));
+ signed char value = -1;
+ if((unsigned)t < sizeof(lookup_table))
+ value = lookup_table[(unsigned)t];
+ if(-1 == value) {
+ isc_throw(isc::BadValue,
+ "attempt to decode a value not in base16 char set");
+ }
+ return (value);
+ }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+// typedef transform_iterator<
+// from_4_bit<CharType>,
+// transform_width<Base, 4, sizeof(Base::value_type) * 8, CharType>
+// > base16_from_binary;
+// but C++ won't accept this. Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor. This makes it incompatible with the dataflow
+// ideal. This is also addressed here.
+
+template<
+ class Base,
+ class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class binary_from_base16 : public
+ transform_iterator<
+ detail::to_4_bit<CharType>,
+ Base
+ >
+{
+ friend class boost::iterator_core_access;
+ typedef transform_iterator<
+ detail::to_4_bit<CharType>,
+ Base
+ > super_t;
+public:
+ // make composable by using templated constructor
+ template<class T>
+ binary_from_base16(T start) :
+ super_t(
+ Base(static_cast<T>(start)),
+ detail::to_4_bit<CharType>()
+ )
+ {}
+ // intel 7.1 doesn't like default copy constructor
+ binary_from_base16(const binary_from_base16 & rhs) :
+ super_t(
+ Base(rhs.base_reference()),
+ detail::to_4_bit<CharType>()
+ )
+ {}
+// binary_from_base16(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE16_HPP
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/binary_from_base32hex.h b/src/lib/util/encode/binary_from_base32hex.h
new file mode 100644
index 0000000..2911789
--- /dev/null
+++ b/src/lib/util/encode/binary_from_base32hex.h
@@ -0,0 +1,115 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE32HEX_HPP
+#define BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE32HEX_HPP
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// binary_from_base32hex.h (derived from boost binary_from_base64.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+// See http://www.boost.org for updates, documentation, and revision history.
+
+#include <cassert>
+
+// We use the same boost header files used in "_from_base64". Since the
+// precise path to these headers may vary depending on the boost version we
+// simply include the base64 header here.
+#include <boost/archive/iterators/binary_from_base64.hpp>
+
+#include <exceptions/exceptions.h>
+
+namespace boost {
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert base32hex characters to binary data
+
+namespace detail {
+
+template<class CharType>
+struct to_5_bit {
+ typedef CharType result_type;
+ CharType operator()(CharType t) const{
+ const signed char lookup_table[] = {
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 00-0f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 10-1f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 20-2f
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, // 30-3f
+ -1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, // 40-4f
+ 25,26,27,28,29,30,31,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 50-5f
+ -1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, // 60-6f
+ 25,26,27,28,29,30,31,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 70-7f
+ };
+ BOOST_STATIC_ASSERT(0x80 == sizeof(lookup_table));
+ signed char value = -1;
+ if((unsigned)t < sizeof(lookup_table))
+ value = lookup_table[(unsigned)t];
+ if(-1 == value) {
+ isc_throw(isc::BadValue,
+ "attempt to decode a value not in base32hex char set");
+ }
+ return (value);
+ }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+// typedef transform_iterator<
+// from_5_bit<CharType>,
+// transform_width<Base, 5, sizeof(Base::value_type) * 8, CharType>
+// > base32hex_from_binary;
+// but C++ won't accept this. Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor. This makes it incompatible with the dataflow
+// ideal. This is also addressed here.
+
+template<
+ class Base,
+ class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class binary_from_base32hex : public
+ transform_iterator<
+ detail::to_5_bit<CharType>,
+ Base
+ >
+{
+ friend class boost::iterator_core_access;
+ typedef transform_iterator<
+ detail::to_5_bit<CharType>,
+ Base
+ > super_t;
+public:
+ // make composable by using templated constructor
+ template<class T>
+ binary_from_base32hex(T start) :
+ super_t(
+ Base(static_cast<T>(start)),
+ detail::to_5_bit<CharType>()
+ )
+ {}
+ // intel 7.1 doesn't like default copy constructor
+ binary_from_base32hex(const binary_from_base32hex & rhs) :
+ super_t(
+ Base(rhs.base_reference()),
+ detail::to_5_bit<CharType>()
+ )
+ {}
+// binary_from_base32hex(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE32HEX_HPP
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/hex.h b/src/lib/util/encode/hex.h
new file mode 100644
index 0000000..de3ac21
--- /dev/null
+++ b/src/lib/util/encode/hex.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2009-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HEX_H
+#define HEX_H 1
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+//
+// Note: this helper module isn't specific to the DNS protocol per se.
+// We should probably move this to somewhere else, possibly in some common
+// utility area.
+//
+
+namespace isc {
+namespace util {
+namespace encode {
+/// \brief Encode binary data in the base16 ('hex') format.
+///
+/// The underlying implementation is shared with \c encodeBase64, and most of
+/// the description except the format (base16) equally applies.
+/// Another notable exception is that the base16 encoding doesn't require
+/// padding, so padding related considerations and the notion of canonical
+/// encoding don't apply.
+///
+/// \param binary A vector object storing the data to be encoded.
+/// \return A newly created string that stores base16 encoded value for
+/// binary.
+std::string encodeHex(const std::vector<uint8_t>& binary);
+
+/// \brief Decode a text encoded in the base16 ('hex') format into the
+/// original %data.
+///
+/// The underlying implementation is shared with \c decodeBase64, and most
+/// of the description except the format (base16) equally applies.
+/// Another notable exception is that the base16 encoding doesn't require
+/// padding, so padding related considerations and the notion of canonical
+/// encoding don't apply.
+///
+/// \param input A text encoded in the base16 format.
+/// \param result A vector in which the decoded %data is to be stored.
+void decodeHex(const std::string& input, std::vector<uint8_t>& result);
+
+/// \brief Encode in hexadecimal inline
+///
+/// \param value the value to encode
+/// \return 0x followed by the value encoded in hexa
+inline std::string toHex(std::string value) {
+ std::vector<uint8_t> bin(value.begin(), value.end());
+ return ("0x" + encodeHex(bin));
+}
+
+} // namespace encode
+} // namespace util
+} // namespace isc
+
+#endif // HEX_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/utf8.cc b/src/lib/util/encode/utf8.cc
new file mode 100644
index 0000000..ac9e0d0
--- /dev/null
+++ b/src/lib/util/encode/utf8.cc
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/encode/utf8.h>
+
+namespace isc {
+namespace util {
+namespace encode {
+
+std::vector<uint8_t> encodeUtf8(const std::string& value) {
+ std::vector<uint8_t> result;
+ if (value.empty()) {
+ return (result);
+ }
+ const uint8_t* start = reinterpret_cast<const uint8_t*>(value.c_str());
+ std::vector<uint8_t> binary(start, start + value.size());
+ for (uint8_t ch : binary) {
+ if (ch < 0x80) {
+ result.push_back(ch);
+ } else {
+ result.push_back(0xc0 | (ch >> 6));
+ result.push_back(0x80 | (ch & 0x3f));
+ }
+ }
+ return (result);
+}
+
+} // namespace encode
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/encode/utf8.h b/src/lib/util/encode/utf8.h
new file mode 100644
index 0000000..9eda471
--- /dev/null
+++ b/src/lib/util/encode/utf8.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UTF8_H
+#define UTF8_H 1
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace util {
+namespace encode {
+/// @brief Encode value string into UTF-8.
+///
+/// @param value A string in latin1 i.e. no encoding.
+/// @return A vector object storing the data encoded in UTF-8.
+std::vector<uint8_t> encodeUtf8(const std::string& value);
+
+} // namespace encode
+} // namespace util
+} // namespace isc
+
+#endif // UTF8_H
diff --git a/src/lib/util/file_utilities.cc b/src/lib/util/file_utilities.cc
new file mode 100644
index 0000000..dfe5e89
--- /dev/null
+++ b/src/lib/util/file_utilities.cc
@@ -0,0 +1,69 @@
+// Copyright (C) 2021-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <util/filename.h>
+#include <cerrno>
+#include <cstring>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace file {
+
+string
+getContent(const string& file_name) {
+ // Open the file.
+ int fd = ::open(file_name.c_str(), O_RDONLY);
+ if (fd < 0) {
+ isc_throw(BadValue, "can't open file '" << file_name << "': "
+ << std::strerror(errno));
+ }
+ try {
+ struct stat stats;
+ if (fstat(fd, &stats) < 0) {
+ isc_throw(BadValue, "can't stat file '" << file_name << "': "
+ << std::strerror(errno));
+ }
+ if ((stats.st_mode & S_IFMT) != S_IFREG) {
+ isc_throw(BadValue, "'" << file_name
+ << "' must be a regular file");
+ }
+ string content(stats.st_size, ' ');
+ ssize_t got = ::read(fd, &content[0], stats.st_size);
+ if (got < 0) {
+ isc_throw(BadValue, "can't read file '" << file_name << "': "
+ << std::strerror(errno));
+ }
+ if (got != stats.st_size) {
+ isc_throw(BadValue, "can't read whole file '" << file_name
+ << "' (got " << got << " of " << stats.st_size << ")");
+ }
+ static_cast<void>(close(fd));
+ return (content);
+ } catch (const std::exception&) {
+ static_cast<void>(close(fd));
+ throw;
+ }
+}
+
+bool
+isDir(const string& name) {
+ struct stat stats;
+ if (::stat(name.c_str(), &stats) < 0) {
+ return (false);
+ }
+ return ((stats.st_mode & S_IFMT) == S_IFDIR);
+}
+
+} // namespace file
+} // namespace log
+} // namespace isc
diff --git a/src/lib/util/file_utilities.h b/src/lib/util/file_utilities.h
new file mode 100644
index 0000000..8c34822
--- /dev/null
+++ b/src/lib/util/file_utilities.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2021-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef FILE_UTILITIES_H
+#define FILE_UTILITIES_H
+
+#include <string>
+
+namespace isc {
+namespace util {
+namespace file {
+
+/// @brief Get the content of a regular file.
+///
+/// @param file_name The file name.
+/// @return The content of the file_name file.
+/// @throw BadValue when the file can't be opened or is not a regular one.
+std::string getContent(const std::string& file_name);
+
+/// @brief Is a directory predicate.
+///
+/// @param name The file or directory name.
+/// @return True if the name points to a directory, false otherwise including
+/// if the pointed location does not exist.
+bool isDir(const std::string& name);
+
+} // namespace file
+} // namespace util
+} // namespace isc
+
+#endif // FILE_UTILITIES_H
diff --git a/src/lib/util/filename.cc b/src/lib/util/filename.cc
new file mode 100644
index 0000000..8cd9cd3
--- /dev/null
+++ b/src/lib/util/filename.cc
@@ -0,0 +1,148 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <iostream>
+#include <algorithm>
+#include <string>
+
+#include <ctype.h>
+
+#include <util/filename.h>
+
+using namespace std;
+
+
+namespace isc {
+namespace util {
+
+// Split string into components. Any backslashes are assumed to have
+// been replaced by forward slashes.
+
+void
+Filename::split(const string& full_name, string& directory, string& name,
+ string& extension) const {
+ directory = name = extension = "";
+ if (!full_name.empty()) {
+
+ bool dir_present = false;
+ // Find the directory.
+ size_t last_slash = full_name.find_last_of('/');
+ if (last_slash != string::npos) {
+
+ // Found the last slash, so extract directory component and
+ // set where the scan for the last_dot should terminate.
+ directory = full_name.substr(0, last_slash + 1);
+ if (last_slash == full_name.size()) {
+
+ // The entire string was a directory, so exit not and don't
+ // do any more searching.
+ return;
+ }
+
+ // Found a directory so note the fact.
+ dir_present = true;
+ }
+
+ // Now search backwards for the last ".".
+ size_t last_dot = full_name.find_last_of('.');
+ if ((last_dot == string::npos) ||
+ (dir_present && (last_dot < last_slash))) {
+
+ // Last "." either not found or it occurs to the left of the last
+ // slash if a directory was present (so it is part of a directory
+ // name). In this case, the remainder of the string after the slash
+ // is the name part.
+ name = full_name.substr(last_slash + 1);
+ return;
+ }
+
+ // Did find a valid dot, so it and everything to the right is the
+ // extension...
+ extension = full_name.substr(last_dot);
+
+ // ... and the name of the file is everything in between.
+ if ((last_dot - last_slash) > 1) {
+ name = full_name.substr(last_slash + 1, last_dot - last_slash - 1);
+ }
+ }
+
+}
+
+// Expand the stored filename with the default.
+
+string
+Filename::expandWithDefault(const string& defname) const {
+
+ string def_directory("");
+ string def_name("");
+ string def_extension("");
+
+ // Normalize the input string.
+ string copy_defname = isc::util::str::trim(defname);
+#ifdef WIN32
+ isc::util::str::normalizeSlash(copy_defname);
+#endif
+
+ // Split into the components
+ split(copy_defname, def_directory, def_name, def_extension);
+
+ // Now construct the result.
+ string retstring =
+ (directory_.empty() ? def_directory : directory_) +
+ (name_.empty() ? def_name : name_) +
+ (extension_.empty() ? def_extension : extension_);
+ return (retstring);
+}
+
+// Use the stored name as default for a given name
+
+string
+Filename::useAsDefault(const string& name) const {
+
+ string name_directory("");
+ string name_name("");
+ string name_extension("");
+
+ // Normalize the input string.
+ string copy_name = isc::util::str::trim(name);
+#ifdef WIN32
+ isc::util::str::normalizeSlash(copy_name);
+#endif
+
+ // Split into the components
+ split(copy_name, name_directory, name_name, name_extension);
+
+ // Now construct the result.
+ string retstring =
+ (name_directory.empty() ? directory_ : name_directory) +
+ (name_name.empty() ? name_ : name_name) +
+ (name_extension.empty() ? extension_ : name_extension);
+ return (retstring);
+}
+
+void
+Filename::setDirectory(const std::string& new_directory) {
+ std::string directory(new_directory);
+
+ if (directory.length() > 0) {
+ // append '/' if necessary
+ size_t sep = directory.rfind('/');
+ if (sep == std::string::npos || sep < directory.size() - 1) {
+ directory += "/";
+ }
+ }
+ // and regenerate the full name
+ std::string full_name = directory + name_ + extension_;
+
+ directory_.swap(directory);
+ full_name_.swap(full_name);
+}
+
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/util/filename.h b/src/lib/util/filename.h
new file mode 100644
index 0000000..ae5ccd2
--- /dev/null
+++ b/src/lib/util/filename.h
@@ -0,0 +1,164 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef FILENAME_H
+#define FILENAME_H
+
+#include <string>
+
+#include <util/strutil.h>
+
+namespace isc {
+namespace util {
+
+/// \brief Class to Manipulate Filenames
+///
+/// This is a utility class to manipulate filenames. It repeats some of the
+/// features found in the Boost filename class, but is self-contained so avoids
+/// the need to link in the Boost library.
+///
+/// A Unix-style filename comprises three parts:
+///
+/// Directory - everything up to and including the last "/". If there is no
+/// "/" in the string, there is no directory component. Note that the
+/// requirement of a trailing slash eliminates the ambiguity of whether a
+/// component is a directory or not, e.g. in /alpha/beta", "beta" could be the
+/// name of a directory or is could be a file. The interpretation here is that
+/// "beta" is the name of a file (although that file could be a directory).
+///
+/// Note: Under Windows, the drive letter is considered to be part of the
+/// directory specification. Unless this class becomes more widely-used on
+/// Windows, there is no point in adding redundant code.
+///
+/// Name - everything from the character after the last "/" up to but not
+/// including the last ".".
+///
+/// Extension - everything from the right-most "." (after the right-most "/") to
+/// the end of the string. If there is no "." after the last "/", there is
+/// no file extension.
+///
+/// (Note that on Windows, this function will replace all "\" characters
+/// with "/" characters on input strings.)
+///
+/// This class provides functions for extracting the components and for
+/// substituting components.
+
+
+class Filename {
+public:
+
+ /// \brief Constructor
+ Filename(const std::string& name) :
+ full_name_(""), directory_(""), name_(""), extension_("") {
+ setName(name);
+ }
+
+ /// \brief Sets Stored Filename
+ ///
+ /// \param name New name to replaced currently stored name
+ void setName(const std::string& name) {
+ full_name_ = isc::util::str::trim(name);
+#ifdef WIN32
+ isc::util::str::normalizeSlash(full_name_);
+#endif
+ split(full_name_, directory_, name_, extension_);
+ }
+
+ /// \return Stored Filename
+ std::string fullName() const {
+ return (full_name_);
+ }
+
+ /// \return Directory of Given File Name
+ std::string directory() const {
+ return (directory_);
+ }
+
+ /// \brief Set directory for the file
+ ///
+ /// \param new_directory The directory to set. If this is an empty
+ /// string, the directory this filename object currently
+ /// has will be removed.
+ void setDirectory(const std::string& new_directory);
+
+ /// \return Name of Given File Name
+ std::string name() const {
+ return (name_);
+ }
+
+ /// \return Extension of Given File Name
+ std::string extension() const {
+ return (extension_);
+ }
+
+ /// \return Name + extension of Given File Name
+ std::string nameAndExtension() const {
+ return (name_ + extension_);
+ }
+
+ /// \brief Expand Name with Default
+ ///
+ /// A default file specified is supplied and used to fill in any missing
+ /// fields. For example, if the name stored is "/a/b" and the supplied
+ /// name is "c.d", the result is "/a/b.d": the only field missing from the
+ /// stored name is the extension, which is supplied by the default.
+ /// Another example would be to store "a.b" and to supply a default of
+ /// "/c/d/" - the result is "/c/d/a.b". (Note that if the supplied default
+ /// was "/c/d", the result would be "/c/a.b", even if "/c/d" were actually
+ /// a directory.)
+ ///
+ /// \param defname Default name
+ ///
+ /// \return Name expanded with defname.
+ std::string expandWithDefault(const std::string& defname) const;
+
+ /// \brief Use as Default and Substitute into String
+ ///
+ /// Does essentially the inverse of expand(); that filled in the stored
+ /// name with a default and returned the result. This treats the stored
+ /// name as the default and uses it to fill in a given name. In essence,
+ /// the code:
+ /// \code
+ /// Filename f("/a/b");
+ /// result = f.expandWithdefault("c.d");
+ /// \endcode
+ /// gives as a result "/a/b.d". This is the same as:
+ /// \code
+ /// Filename f("c.d");
+ /// result = f.useAsDefault("/a/b");
+ /// \endcode
+ ///
+ /// \param name Name to expand
+ ///
+ /// \return Name expanded with stored name
+ std::string useAsDefault(const std::string& name) const;
+
+private:
+ /// \brief Split Name into Components
+ ///
+ /// Splits the file name into the directory, name and extension parts.
+ /// The name is assumed to have had back slashes replaced by forward
+ /// slashes (if appropriate).
+ ///
+ /// \param full_name Name to split
+ /// \param directory Returned directory part
+ /// \param name Returned name part
+ /// \param extension Returned extension part
+ void split(const std::string& full_name, std::string& directory,
+ std::string& name, std::string& extension) const;
+
+ // Members
+
+ std::string full_name_; ///< Given name
+ std::string directory_; ///< Directory part
+ std::string name_; ///< Name part
+ std::string extension_; ///< Extension part
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // FILENAME_H
diff --git a/src/lib/util/hash.h b/src/lib/util/hash.h
new file mode 100644
index 0000000..45d6ba5
--- /dev/null
+++ b/src/lib/util/hash.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HASH_H
+#define HASH_H
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Hash implementation based on Fowler-Noll-Vo hash function
+///
+struct Hash64 {
+ /// @brief Compute the hash
+ ///
+ /// FNV-1a hash function
+ ///
+ /// @param data data to hash
+ /// @param length length of data
+ /// @return the hash value
+ static uint64_t hash(const uint8_t* data, size_t length) {
+ uint64_t hash = FNV_offset_basis;
+ for (size_t i = 0; i < length; ++i) {
+ hash = hash ^ data[i];
+ hash = hash * FNV_prime;
+ }
+ return (hash);
+ }
+
+ /// @brief Compute the hash
+ ///
+ /// FNV-1a hash function
+ ///
+ /// @param str not empty string to hash
+ /// @return the hash value
+ static uint64_t hash(const std::string& str) {
+ return (hash(reinterpret_cast<const uint8_t*>(str.c_str()),
+ str.size()));
+ }
+
+ /// @brief Offset basis
+ static const uint64_t FNV_offset_basis = 14695981039346656037ull;
+
+ /// @brief Prime
+ static const uint64_t FNV_prime = 1099511628211ull;
+};
+
+} // end of namespace isc::util
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/util/io/Makefile.am b/src/lib/util/io/Makefile.am
new file mode 100644
index 0000000..b5659b9
--- /dev/null
+++ b/src/lib/util/io/Makefile.am
@@ -0,0 +1,14 @@
+SUBDIRS = .
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+lib_LTLIBRARIES = libkea-util-io.la
+libkea_util_io_la_SOURCES = fd.h fd.cc fd_share.h fd_share.cc
+libkea_util_io_la_SOURCES += socketsession.h socketsession.cc sockaddr_util.h
+libkea_util_io_la_SOURCES += pktinfo_utilities.h
+libkea_util_io_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_util_io_la_LDFLAGS = -no-undefined -version-info 0:1:0
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/io/Makefile.in b/src/lib/util/io/Makefile.in
new file mode 100644
index 0000000..fb2d9e9
--- /dev/null
+++ b/src/lib/util/io/Makefile.in
@@ -0,0 +1,914 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/util/io
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libkea_util_io_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+am_libkea_util_io_la_OBJECTS = fd.lo fd_share.lo socketsession.lo
+libkea_util_io_la_OBJECTS = $(am_libkea_util_io_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libkea_util_io_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_util_io_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/fd.Plo ./$(DEPDIR)/fd_share.Plo \
+ ./$(DEPDIR)/socketsession.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libkea_util_io_la_SOURCES)
+DIST_SOURCES = $(libkea_util_io_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+lib_LTLIBRARIES = libkea-util-io.la
+libkea_util_io_la_SOURCES = fd.h fd.cc fd_share.h fd_share.cc \
+ socketsession.h socketsession.cc sockaddr_util.h \
+ pktinfo_utilities.h
+libkea_util_io_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_util_io_la_LDFLAGS = -no-undefined -version-info 0:1:0
+CLEANFILES = *.gcno *.gcda
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/util/io/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/io/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libkea-util-io.la: $(libkea_util_io_la_OBJECTS) $(libkea_util_io_la_DEPENDENCIES) $(EXTRA_libkea_util_io_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_util_io_la_LINK) -rpath $(libdir) $(libkea_util_io_la_OBJECTS) $(libkea_util_io_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fd_share.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socketsession.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/fd.Plo
+ -rm -f ./$(DEPDIR)/fd_share.Plo
+ -rm -f ./$(DEPDIR)/socketsession.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/fd.Plo
+ -rm -f ./$(DEPDIR)/fd_share.Plo
+ -rm -f ./$(DEPDIR)/socketsession.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-libLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/util/io/fd.cc b/src/lib/util/io/fd.cc
new file mode 100644
index 0000000..9d89485
--- /dev/null
+++ b/src/lib/util/io/fd.cc
@@ -0,0 +1,78 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/io/fd.h>
+
+#include <unistd.h>
+#include <cerrno>
+
+namespace isc {
+namespace util {
+namespace io {
+
+bool
+write_data(const int fd, const void *buffer_v, const size_t length) {
+ const unsigned char* buffer(static_cast<const unsigned char*>(buffer_v));
+ size_t remaining = length; // Amount remaining to be written
+
+ // Just keep writing until all is written
+ while (remaining > 0) {
+ const int written = write(fd, buffer, remaining);
+ if (written == -1) {
+ if (errno == EINTR) { // Just keep going
+ continue;
+ } else {
+ return (false);
+ }
+
+ } else if (written > 0) {
+ // Wrote "written" bytes from the buffer
+ remaining -= written;
+ buffer += written;
+
+ } else {
+ // Wrote zero bytes from the buffer. We should not get here as any
+ // error that causes zero bytes to be written should have returned
+ // -1. However, write(2) can return 0, and in this case we
+ // interpret it as an error.
+ return (false);
+ }
+ }
+ return (true);
+}
+
+ssize_t
+read_data(const int fd, void *buffer_v, const size_t length) {
+ unsigned char* buffer(static_cast<unsigned char*>(buffer_v));
+ size_t remaining = length; // Amount remaining to be read
+
+ while (remaining > 0) {
+ const int amount = read(fd, buffer, remaining);
+ if (amount == -1) {
+ if (errno == EINTR) { // Continue on interrupted call
+ continue;
+ } else {
+ return (-1);
+ }
+ } else if (amount > 0) {
+ // Read "amount" bytes into the buffer
+ remaining -= amount;
+ buffer += amount;
+ } else {
+ // EOF - end the read
+ break;
+ }
+ }
+
+ // Return total number of bytes read
+ return (static_cast<ssize_t>(length - remaining));
+}
+
+}
+}
+}
diff --git a/src/lib/util/io/fd.h b/src/lib/util/io/fd.h
new file mode 100644
index 0000000..b647425
--- /dev/null
+++ b/src/lib/util/io/fd.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UTIL_IO_FD_H
+#define UTIL_IO_FD_H 1
+
+#include <unistd.h>
+
+/**
+ * @file fd.h
+ * @short Wrappers around common unix fd manipulation functions.
+ */
+
+namespace isc {
+namespace util {
+namespace io {
+
+/*
+ * \short write() that writes everything.
+ * Wrapper around write(). The difference is, it never writes less data
+ * and looks successful (eg. it blocks until all data are written).
+ * Retries on signals.
+ *
+ * \return True if successful, false otherwise. The errno variable is left
+ * intact.
+ * \param fd Where to write.
+ * \param data The buffer to write.
+ * \param length How much data is there to write.
+ */
+bool
+write_data(const int fd, const void *data, const size_t length);
+
+/*
+ * \short read() that reads everything.
+ * Wrapper around read(). It does not do short reads, if it returns less,
+ * it means there was EOF. It retries on signals.
+ *
+ * \return Number of bytes read or -1 on error.
+ * \param fd Where to read data from.
+ * \param data Where to put the data.
+ * \param length How many of them.
+ */
+ssize_t
+read_data(const int fd, void *buffer, const size_t length);
+
+}
+}
+}
+
+#endif // UTIL_IO_FD_H
diff --git a/src/lib/util/io/fd_share.cc b/src/lib/util/io/fd_share.cc
new file mode 100644
index 0000000..6edffd3
--- /dev/null
+++ b/src/lib/util/io/fd_share.cc
@@ -0,0 +1,166 @@
+// Copyright (C) 2010-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cstring>
+#include <cstdlib>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <stdlib.h> // for malloc and free
+#include <unistd.h>
+#include <util/io/fd_share.h>
+
+namespace isc {
+namespace util {
+namespace io {
+
+namespace {
+// Not all OSes support advanced CMSG macros: CMSG_LEN and CMSG_SPACE.
+// In order to ensure as much portability as possible, we provide wrapper
+// functions of these macros.
+// Note that cmsg_space() could run slow on OSes that do not have
+// CMSG_SPACE.
+inline socklen_t
+cmsg_len(const socklen_t len) {
+#ifdef CMSG_LEN
+ return (CMSG_LEN(len));
+#else
+ // Cast NULL so that any pointer arithmetic performed by CMSG_DATA
+ // is correct.
+ const uintptr_t hdrlen = (uintptr_t)CMSG_DATA(((struct cmsghdr*)NULL));
+ return (hdrlen + len);
+#endif
+}
+
+inline socklen_t
+cmsg_space(const socklen_t len) {
+#ifdef CMSG_SPACE
+ return (CMSG_SPACE(len));
+#else
+ struct msghdr msg;
+ struct cmsghdr* cmsgp;
+ // XXX: The buffer length is an ad hoc value, but should be enough
+ // in a practical sense.
+ char dummybuf[sizeof(struct cmsghdr) + 1024];
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_control = dummybuf;
+ msg.msg_controllen = sizeof(dummybuf);
+
+ cmsgp = (struct cmsghdr*)dummybuf;
+ cmsgp->cmsg_len = cmsg_len(len);
+
+ cmsgp = CMSG_NXTHDR(&msg, cmsgp);
+ if (cmsgp != NULL) {
+ return ((char*)cmsgp - (char*)msg.msg_control);
+ } else {
+ return (0);
+ }
+#endif // CMSG_SPACE
+}
+}
+
+int
+recv_fd(const int sock) {
+ struct msghdr msghdr;
+ struct iovec iov_dummy;
+ unsigned char dummy_data;
+
+ iov_dummy.iov_base = &dummy_data;
+ iov_dummy.iov_len = sizeof(dummy_data);
+ msghdr.msg_name = NULL;
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = &iov_dummy;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_flags = 0;
+ msghdr.msg_controllen = cmsg_space(sizeof(int));
+ msghdr.msg_control = malloc(msghdr.msg_controllen);
+ if (msghdr.msg_control == NULL) {
+ return (FD_SYSTEM_ERROR);
+ }
+
+ const int cc = recvmsg(sock, &msghdr, 0);
+ if (cc <= 0) {
+ free(msghdr.msg_control);
+ if (cc == 0) {
+ errno = ECONNRESET;
+ }
+ return (FD_SYSTEM_ERROR);
+ }
+ const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
+ int fd = FD_OTHER_ERROR;
+ if (cmsg != NULL && cmsg->cmsg_len == cmsg_len(sizeof(int)) &&
+ cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+// Some systems (e.g. recent NetBSD) converted all CMSG access macros
+// to static_cast when used in C++ code. As cmsg is declared const
+// this makes the CMSG_DATA macro to not compile. But fortunately
+// these systems provide a const alternative named CCMSG_DATA.
+#ifdef CCMSG_DATA
+ std::memcpy(&fd, CCMSG_DATA(cmsg), sizeof(int));
+#else
+ std::memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
+#endif
+ }
+ free(msghdr.msg_control);
+ int new_fd = -1;
+ int close_error = -1;
+ if (fd >= 0) {
+ // It is strange, but the call can return the same file descriptor as
+ // one returned previously, even if that one is not closed yet. So,
+ // we just re-number every one we get, so they are unique.
+ new_fd = dup(fd);
+ close_error = close(fd);
+ }
+ if (close_error == -1 || new_fd == -1) {
+ // We need to return an error, because something failed. But in case
+ // it was the previous close, we at least try to close the duped FD.
+ if (new_fd != -1) {
+ close(new_fd); // If this fails, nothing but returning error can't
+ // be done and we are doing that anyway.
+ }
+ return (FD_SYSTEM_ERROR);
+ }
+ return (new_fd);
+}
+
+int
+send_fd(const int sock, const int fd) {
+ struct msghdr msghdr;
+ struct iovec iov_dummy;
+ unsigned char dummy_data = 0;
+
+ iov_dummy.iov_base = &dummy_data;
+ iov_dummy.iov_len = sizeof(dummy_data);
+ msghdr.msg_name = NULL;
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = &iov_dummy;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_flags = 0;
+ msghdr.msg_controllen = cmsg_space(sizeof(int));
+ msghdr.msg_control = malloc(msghdr.msg_controllen);
+ if (msghdr.msg_control == NULL) {
+ return (FD_OTHER_ERROR);
+ }
+ std::memset(msghdr.msg_control, 0, msghdr.msg_controllen);
+
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
+ cmsg->cmsg_len = cmsg_len(sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ std::memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+ const int ret = sendmsg(sock, &msghdr, 0);
+ free(msghdr.msg_control);
+ return (ret >= 0 ? 0 : FD_SYSTEM_ERROR);
+}
+
+} // End for namespace io
+} // End for namespace util
+} // End for namespace isc
diff --git a/src/lib/util/io/fd_share.h b/src/lib/util/io/fd_share.h
new file mode 100644
index 0000000..36d0d5f
--- /dev/null
+++ b/src/lib/util/io/fd_share.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef FD_SHARE_H_
+#define FD_SHARE_H_
+
+/**
+ * \file fd_share.h
+ * \short Support to transfer file descriptors between processes.
+ * \todo This interface is very C-ish. Should we have some kind of exceptions?
+ */
+
+namespace isc {
+namespace util {
+namespace io {
+
+const int FD_SYSTEM_ERROR = -2;
+const int FD_OTHER_ERROR = -1;
+
+/**
+ * \short Receives a file descriptor.
+ * This receives a file descriptor sent over an unix domain socket. This
+ * is the counterpart of send_fd().
+ *
+ * \return FD_SYSTEM_ERROR when there's an error at the operating system
+ * level (such as a system call failure). The global 'errno' variable
+ * indicates the specific error. FD_OTHER_ERROR when there's a different
+ * error.
+ *
+ * \param sock The unix domain socket to read from. Tested and it does
+ * not work with a pipe.
+ */
+int recv_fd(const int sock);
+
+/**
+ * \short Sends a file descriptor.
+ * This sends a file descriptor over an unix domain socket. This is the
+ * counterpart of recv_fd().
+ *
+ * \return FD_SYSTEM_ERROR when there's an error at the operating system
+ * level (such as a system call failure). The global 'errno' variable
+ * indicates the specific error.
+ * \param sock The unix domain socket to send to. Tested and it does not
+ * work with a pipe.
+ * \param fd The file descriptor to send. It should work with any valid
+ * file descriptor.
+ */
+int send_fd(const int sock, const int fd);
+
+} // End for namespace io
+} // End for namespace util
+} // End for namespace isc
+
+#endif
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/io/pktinfo_utilities.h b/src/lib/util/io/pktinfo_utilities.h
new file mode 100644
index 0000000..2625008
--- /dev/null
+++ b/src/lib/util/io/pktinfo_utilities.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PKTINFO_UTIL_H
+#define PKTINFO_UTIL_H 1
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+// These definitions in this file are for the convenience of internal
+// implementation and test code, and are not intended to be used publicly.
+// The namespace "internal" indicates the intent.
+
+namespace isc {
+namespace util {
+namespace io {
+namespace internal {
+
+// Lower level C-APIs require conversion between char* pointers
+// (like structures returned by CMSG_DATA macro) and in6_pktinfo,
+// which is not friendly with C++. The following templates
+// are a shortcut of common workaround conversion in such cases.
+inline struct in6_pktinfo*
+convertPktInfo6(char* pktinfo) {
+ return (static_cast<struct in6_pktinfo*>(static_cast<void*>(pktinfo)));
+}
+
+inline struct in6_pktinfo*
+convertPktInfo6(unsigned char* pktinfo) {
+ return (static_cast<struct in6_pktinfo*>(static_cast<void*>(pktinfo)));
+}
+
+/// @todo: Do we need const versions as well?
+
+}
+}
+}
+}
+
+#endif // PKTINFO_UTIL_H
diff --git a/src/lib/util/io/sockaddr_util.h b/src/lib/util/io/sockaddr_util.h
new file mode 100644
index 0000000..1cb31f2
--- /dev/null
+++ b/src/lib/util/io/sockaddr_util.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SOCKADDR_UTIL_H
+#define SOCKADDR_UTIL_H 1
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <exceptions/isc_assert.h>
+
+// These definitions in this file are for the convenience of internal
+// implementation and test code, and are not intended to be used publicly.
+// The namespace "internal" indicates the intent.
+
+namespace isc {
+namespace util {
+namespace io {
+namespace internal {
+
+inline socklen_t
+getSALength(const struct sockaddr& sa) {
+ if (sa.sa_family == AF_INET) {
+ return (sizeof(struct sockaddr_in));
+ } else {
+ isc_throw_assert(sa.sa_family == AF_INET6);
+ return (sizeof(struct sockaddr_in6));
+ }
+}
+
+// Lower level C-APIs require conversion between various variants of
+// sockaddr's, which is not friendly with C++. The following templates
+// are a shortcut of common workaround conversion in such cases.
+
+template <typename SAType>
+const struct sockaddr*
+convertSockAddr(const SAType* sa) {
+ const void* p = sa;
+ return (static_cast<const struct sockaddr*>(p));
+}
+
+template <typename SAType>
+const SAType*
+convertSockAddr(const struct sockaddr* sa) {
+ const void* p = sa;
+ return (static_cast<const SAType*>(p));
+}
+
+template <typename SAType>
+struct sockaddr*
+convertSockAddr(SAType* sa) {
+ void* p = sa;
+ return (static_cast<struct sockaddr*>(p));
+}
+
+template <typename SAType>
+SAType*
+convertSockAddr(struct sockaddr* sa) {
+ void* p = sa;
+ return (static_cast<SAType*>(p));
+}
+
+}
+}
+}
+}
+
+#endif // SOCKADDR_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/io/socketsession.cc b/src/lib/util/io/socketsession.cc
new file mode 100644
index 0000000..88f2b1e
--- /dev/null
+++ b/src/lib/util/io/socketsession.cc
@@ -0,0 +1,438 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+
+#include <cerrno>
+#include <csignal>
+#include <cstddef>
+#include <cstring>
+
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+
+#include <util/buffer.h>
+
+#include <util/io/fd_share.h>
+#include <util/io/socketsession.h>
+#include <util/io/sockaddr_util.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace io {
+
+using namespace internal;
+
+// The expected max size of the session header: 2-byte header length,
+// 6 32-bit fields, and 2 sockaddr structure. (see the SocketSessionUtility
+// overview description in the header file). sizeof sockaddr_storage
+// should be the possible max of any sockaddr structure
+const size_t DEFAULT_HEADER_BUFLEN = sizeof(uint16_t) + sizeof(uint32_t) * 6 +
+ sizeof(struct sockaddr_storage) * 2;
+
+// The allowable maximum size of data passed with the socket FD. For now
+// we use a fixed value of 65535, the largest possible size of valid DNS
+// messages. We may enlarge it or make it configurable as we see the need
+// for more flexibility.
+const int MAX_DATASIZE = 65535;
+
+// The initial buffer size for receiving socket session data in the receiver.
+// This value is the maximum message size of DNS messages carried over UDP
+// (without EDNS). In our expected usage (at the moment) this should be
+// sufficiently large (the expected data is AXFR/IXFR query or an UPDATE
+// requests. The former should be generally quite small. While the latter
+// could be large, it would often be small enough for a single UDP message).
+// If it turns out that there are many exceptions, we may want to extend
+// the class so that this value can be customized. Note that the buffer
+// will be automatically extended for longer data and this is only about
+// efficiency.
+const size_t INITIAL_BUFSIZE = 512;
+
+// The (default) socket buffer size for the forwarder and receiver. This is
+// chosen to be sufficiently large to store two full-size DNS messages. We
+// may want to customize this value in future.
+const int SOCKSESSION_BUFSIZE = (DEFAULT_HEADER_BUFLEN + MAX_DATASIZE) * 2;
+
+struct SocketSessionForwarder::ForwarderImpl {
+ ForwarderImpl() : sock_un_len_(0), fd_(-1), buf_(DEFAULT_HEADER_BUFLEN) {
+ memset(&sock_un_, 0, sizeof(sock_un_));
+ }
+
+ struct sockaddr_un sock_un_;
+ socklen_t sock_un_len_;
+ int fd_;
+ OutputBuffer buf_;
+};
+
+SocketSessionForwarder::SocketSessionForwarder(const std::string& unix_file) :
+ impl_(NULL)
+{
+ // We need to filter SIGPIPE for subsequent push(). See the class
+ // description.
+ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
+ isc_throw(Unexpected, "Failed to filter SIGPIPE: " << strerror(errno));
+ }
+
+ ForwarderImpl impl;
+ if (sizeof(impl.sock_un_.sun_path) - 1 < unix_file.length()) {
+ isc_throw(SocketSessionError,
+ "File name for a UNIX domain socket is too long: " <<
+ unix_file);
+ }
+ impl.sock_un_.sun_family = AF_UNIX;
+ // the copy should be safe due to the above check, but we'd be rather
+ // paranoid about making it 100% sure even if the check has a bug (with
+ // triggering the assertion in the worse case)
+ memset(&impl.sock_un_.sun_path, 0, sizeof(impl.sock_un_.sun_path));
+ strncpy(impl.sock_un_.sun_path, unix_file.c_str(),
+ sizeof(impl.sock_un_.sun_path) - 1);
+ isc_throw_assert(impl.sock_un_.sun_path[sizeof(impl.sock_un_.sun_path) - 1] == '\0');
+ impl.sock_un_len_ = offsetof(struct sockaddr_un, sun_path) +
+ unix_file.length();
+#ifdef HAVE_SA_LEN
+ impl.sock_un_.sun_len = impl.sock_un_len_;
+#endif
+ impl.fd_ = -1;
+
+ impl_ = new ForwarderImpl;
+ *impl_ = impl;
+}
+
+SocketSessionForwarder::~SocketSessionForwarder() {
+ if (impl_->fd_ != -1) {
+ close();
+ }
+ delete impl_;
+}
+
+void
+SocketSessionForwarder::connectToReceiver() {
+ if (impl_->fd_ != -1) {
+ isc_throw(BadValue, "Duplicate connect to UNIX domain "
+ "endpoint " << impl_->sock_un_.sun_path);
+ }
+
+ impl_->fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (impl_->fd_ == -1) {
+ isc_throw(SocketSessionError, "Failed to create a UNIX domain socket: "
+ << strerror(errno));
+ }
+ // Make the socket non blocking
+ int fcntl_flags = fcntl(impl_->fd_, F_GETFL, 0);
+ if (fcntl_flags != -1) {
+ fcntl_flags |= O_NONBLOCK;
+ fcntl_flags = fcntl(impl_->fd_, F_SETFL, fcntl_flags);
+ }
+ if (fcntl_flags == -1) {
+ close(); // note: this is the internal method, not ::close()
+ isc_throw(SocketSessionError,
+ "Failed to make UNIX domain socket non blocking: " <<
+ strerror(errno));
+ }
+ // Ensure the socket send buffer is large enough. If we can't get the
+ // current size, simply set the sufficient size.
+ int sndbuf_size;
+ socklen_t sndbuf_size_len = sizeof(sndbuf_size);
+ if (getsockopt(impl_->fd_, SOL_SOCKET, SO_SNDBUF, &sndbuf_size,
+ &sndbuf_size_len) == -1 ||
+ sndbuf_size < SOCKSESSION_BUFSIZE) {
+ if (setsockopt(impl_->fd_, SOL_SOCKET, SO_SNDBUF, &SOCKSESSION_BUFSIZE,
+ sizeof(SOCKSESSION_BUFSIZE)) == -1) {
+ close();
+ isc_throw(SocketSessionError,
+ "Failed to set send buffer size to " <<
+ SOCKSESSION_BUFSIZE);
+ }
+ }
+ if (connect(impl_->fd_, convertSockAddr(&impl_->sock_un_),
+ impl_->sock_un_len_) == -1) {
+ close();
+ isc_throw(SocketSessionError, "Failed to connect to UNIX domain "
+ "endpoint " << impl_->sock_un_.sun_path << ": " <<
+ strerror(errno));
+ }
+}
+
+void
+SocketSessionForwarder::close() {
+ if (impl_->fd_ == -1) {
+ isc_throw(BadValue, "Attempt of close before connect");
+ }
+ ::close(impl_->fd_);
+ impl_->fd_ = -1;
+}
+
+void
+SocketSessionForwarder::push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len)
+{
+ if (impl_->fd_ == -1) {
+ isc_throw(BadValue, "Attempt of push before connect");
+ }
+ if ((local_end.sa_family != AF_INET && local_end.sa_family != AF_INET6) ||
+ (remote_end.sa_family != AF_INET && remote_end.sa_family != AF_INET6))
+ {
+ isc_throw(BadValue, "Invalid address family: must be "
+ "AF_INET or AF_INET6; " <<
+ static_cast<int>(local_end.sa_family) << ", " <<
+ static_cast<int>(remote_end.sa_family) << " given");
+ }
+ if (family != local_end.sa_family || family != remote_end.sa_family) {
+ isc_throw(BadValue, "Inconsistent address family: must be "
+ << static_cast<int>(family) << "; "
+ << static_cast<int>(local_end.sa_family) << ", "
+ << static_cast<int>(remote_end.sa_family) << " given");
+ }
+ if (data_len == 0 || data == NULL) {
+ isc_throw(BadValue, "Data for a socket session must not be empty");
+ }
+ if (data_len > MAX_DATASIZE) {
+ isc_throw(BadValue, "Invalid socket session data size: " <<
+ data_len << ", must not exceed " << MAX_DATASIZE);
+ }
+
+ if (send_fd(impl_->fd_, sock) != 0) {
+ isc_throw(SocketSessionError, "FD passing failed: " <<
+ strerror(errno));
+ }
+
+ impl_->buf_.clear();
+ // Leave the space for the header length
+ impl_->buf_.skip(sizeof(uint16_t));
+ // Socket properties: family, type, protocol
+ impl_->buf_.writeUint32(static_cast<uint32_t>(family));
+ impl_->buf_.writeUint32(static_cast<uint32_t>(type));
+ impl_->buf_.writeUint32(static_cast<uint32_t>(protocol));
+ // Local endpoint
+ impl_->buf_.writeUint32(static_cast<uint32_t>(getSALength(local_end)));
+ impl_->buf_.writeData(&local_end, getSALength(local_end));
+ // Remote endpoint
+ impl_->buf_.writeUint32(static_cast<uint32_t>(getSALength(remote_end)));
+ impl_->buf_.writeData(&remote_end, getSALength(remote_end));
+ // Data length. Must be fit uint32 due to the range check above.
+ const uint32_t data_len32 = static_cast<uint32_t>(data_len);
+ isc_throw_assert(data_len == data_len32); // shouldn't cause overflow.
+ impl_->buf_.writeUint32(data_len32);
+ // Write the resulting header length at the beginning of the buffer
+ impl_->buf_.writeUint16At(impl_->buf_.getLength() - sizeof(uint16_t), 0);
+
+ const struct iovec iov[2] = {
+ { const_cast<void*>(impl_->buf_.getData()), impl_->buf_.getLength() },
+ { const_cast<void*>(data), data_len }
+ };
+ const int cc = writev(impl_->fd_, iov, 2);
+ if (cc != impl_->buf_.getLength() + data_len) {
+ if (cc < 0) {
+ isc_throw(SocketSessionError,
+ "Write failed in forwarding a socket session: " <<
+ strerror(errno));
+ }
+ isc_throw(SocketSessionError,
+ "Incomplete write in forwarding a socket session: " << cc <<
+ "/" << (impl_->buf_.getLength() + data_len));
+ }
+}
+
+SocketSession::SocketSession(int sock, int family, int type, int protocol,
+ const sockaddr* local_end,
+ const sockaddr* remote_end,
+ const void* data, size_t data_len) :
+ sock_(sock), family_(family), type_(type), protocol_(protocol),
+ local_end_(local_end), remote_end_(remote_end),
+ data_(data), data_len_(data_len)
+{
+ if (local_end == NULL || remote_end == NULL) {
+ isc_throw(BadValue, "sockaddr must be non NULL for SocketSession");
+ }
+ if (data_len == 0) {
+ isc_throw(BadValue, "data_len must be non 0 for SocketSession");
+ }
+ if (data == NULL) {
+ isc_throw(BadValue, "data must be non NULL for SocketSession");
+ }
+}
+
+struct SocketSessionReceiver::ReceiverImpl {
+ ReceiverImpl(int fd) : fd_(fd),
+ sa_local_(convertSockAddr(&ss_local_)),
+ sa_remote_(convertSockAddr(&ss_remote_)),
+ header_buf_(DEFAULT_HEADER_BUFLEN),
+ data_buf_(INITIAL_BUFSIZE)
+ {
+ memset(&ss_local_, 0, sizeof(ss_local_));
+ memset(&ss_remote_, 0, sizeof(ss_remote_));
+
+ if (setsockopt(fd_, SOL_SOCKET, SO_RCVBUF, &SOCKSESSION_BUFSIZE,
+ sizeof(SOCKSESSION_BUFSIZE)) == -1) {
+ isc_throw(SocketSessionError,
+ "Failed to set receive buffer size to " <<
+ SOCKSESSION_BUFSIZE);
+ }
+ }
+
+ const int fd_;
+ struct sockaddr_storage ss_local_; // placeholder for local endpoint
+ struct sockaddr* const sa_local_;
+ struct sockaddr_storage ss_remote_; // placeholder for remote endpoint
+ struct sockaddr* const sa_remote_;
+
+ // placeholder for session header and data
+ vector<uint8_t> header_buf_;
+ vector<uint8_t> data_buf_;
+};
+
+SocketSessionReceiver::SocketSessionReceiver(int fd) :
+ impl_(new ReceiverImpl(fd))
+{
+}
+
+SocketSessionReceiver::~SocketSessionReceiver() {
+ delete impl_;
+}
+
+namespace {
+// A shortcut to throw common exception on failure of recv(2)
+void
+readFail(int actual_len, int expected_len) {
+ if (expected_len < 0) {
+ isc_throw(SocketSessionError, "Failed to receive data from "
+ "SocketSessionForwarder: " << strerror(errno));
+ }
+ isc_throw(SocketSessionError, "Incomplete data from "
+ "SocketSessionForwarder: " << actual_len << "/" <<
+ expected_len);
+}
+
+// A helper container for a (socket) file descriptor used in
+// SocketSessionReceiver::pop that ensures the socket is closed unless it
+// can be safely passed to the caller via release().
+struct ScopedSocket : boost::noncopyable {
+ ScopedSocket(int fd) : fd_(fd) {}
+ ~ScopedSocket() {
+ if (fd_ >= 0) {
+ close(fd_);
+ }
+ }
+ int release() {
+ const int fd = fd_;
+ fd_ = -1;
+ return (fd);
+ }
+ int fd_;
+};
+}
+
+SocketSession
+SocketSessionReceiver::pop() {
+ ScopedSocket passed_sock(recv_fd(impl_->fd_));
+ if (passed_sock.fd_ == FD_SYSTEM_ERROR) {
+ isc_throw(SocketSessionError, "Receiving a forwarded FD failed: " <<
+ strerror(errno));
+ } else if (passed_sock.fd_ < 0) {
+ isc_throw(SocketSessionError, "No FD forwarded");
+ }
+
+ uint16_t header_len;
+ const int cc_hlen = recv(impl_->fd_, &header_len, sizeof(header_len),
+ MSG_WAITALL);
+ if (cc_hlen < sizeof(header_len)) {
+ readFail(cc_hlen, sizeof(header_len));
+ }
+ header_len = InputBuffer(&header_len, sizeof(header_len)).readUint16();
+ if (header_len > DEFAULT_HEADER_BUFLEN) {
+ isc_throw(SocketSessionError, "Too large header length: " <<
+ header_len);
+ }
+ impl_->header_buf_.clear();
+ impl_->header_buf_.resize(header_len);
+ const int cc_hdr = recv(impl_->fd_, &impl_->header_buf_[0], header_len,
+ MSG_WAITALL);
+ if (cc_hdr < header_len) {
+ readFail(cc_hdr, header_len);
+ }
+
+ InputBuffer ibuffer(&impl_->header_buf_[0], header_len);
+ try {
+ const int family = static_cast<int>(ibuffer.readUint32());
+ if (family != AF_INET && family != AF_INET6) {
+ isc_throw(SocketSessionError,
+ "Unsupported address family is passed: " << family);
+ }
+ const int type = static_cast<int>(ibuffer.readUint32());
+ const int protocol = static_cast<int>(ibuffer.readUint32());
+ const socklen_t local_end_len = ibuffer.readUint32();
+ const socklen_t endpoint_minlen = (family == AF_INET) ?
+ sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
+ if (local_end_len < endpoint_minlen ||
+ local_end_len > sizeof(impl_->ss_local_)) {
+ isc_throw(SocketSessionError, "Invalid local SA length: " <<
+ local_end_len);
+ }
+ ibuffer.readData(&impl_->ss_local_, local_end_len);
+ const socklen_t remote_end_len = ibuffer.readUint32();
+ if (remote_end_len < endpoint_minlen ||
+ remote_end_len > sizeof(impl_->ss_remote_)) {
+ isc_throw(SocketSessionError, "Invalid remote SA length: " <<
+ remote_end_len);
+ }
+ ibuffer.readData(&impl_->ss_remote_, remote_end_len);
+ if (family != impl_->sa_local_->sa_family ||
+ family != impl_->sa_remote_->sa_family) {
+ isc_throw(SocketSessionError, "SA family inconsistent: " <<
+ static_cast<int>(impl_->sa_local_->sa_family) << ", " <<
+ static_cast<int>(impl_->sa_remote_->sa_family) <<
+ " given, must be " << family);
+ }
+ const size_t data_len = ibuffer.readUint32();
+ if (data_len == 0 || data_len > MAX_DATASIZE) {
+ isc_throw(SocketSessionError,
+ "Invalid socket session data size: " << data_len <<
+ ", must be > 0 and <= " << MAX_DATASIZE);
+ }
+
+ impl_->data_buf_.clear();
+ impl_->data_buf_.resize(data_len);
+ const int cc_data = recv(impl_->fd_, &impl_->data_buf_[0], data_len,
+ MSG_WAITALL);
+ if (cc_data < data_len) {
+ readFail(cc_data, data_len);
+ }
+
+ return (SocketSession(passed_sock.release(), family, type, protocol,
+ impl_->sa_local_, impl_->sa_remote_,
+ &impl_->data_buf_[0], data_len));
+ } catch (const InvalidBufferPosition& ex) {
+ // We catch the case where the given header is too short and convert
+ // the exception to SocketSessionError.
+ isc_throw(SocketSessionError, "bogus socket session header: " <<
+ ex.what());
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/util/io/socketsession.h b/src/lib/util/io/socketsession.h
new file mode 100644
index 0000000..52e33de
--- /dev/null
+++ b/src/lib/util/io/socketsession.h
@@ -0,0 +1,495 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SOCKETSESSION_H
+#define SOCKETSESSION_H 1
+
+#include <boost/noncopyable.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+#include <sys/socket.h>
+
+namespace isc {
+namespace util {
+namespace io {
+
+/// \page SocketSessionUtility Socket session utility
+///
+/// \note This class is currently unused. Once we get to the implementation
+/// of the remote parts of the management API, we will evaluate whether
+/// this code is useful or not and either start using it or remove it.
+///
+/// This utility defines a set of classes that support forwarding a
+/// "socket session" from one process to another. A socket session is a
+/// conceptual tuple of the following elements:
+/// - A network socket
+/// - The local and remote endpoints of a (IP) communication taking place on
+/// the socket. In practice an endpoint is a pair of an IP address and
+/// TCP or UDP port number.
+/// - Some amount of data sent from the remote endpoint and received on the
+/// socket. We call it (socket) session data in this documentation.
+///
+/// Note that this is a conceptual definition. Depending on the underlying
+/// implementation and/or the network protocol, some of the elements could be
+/// part of others; for example, if it's an established TCP connection,
+/// the local and remote endpoints would be able to be retrieved from the
+/// socket using the standard \c getsockname() and \c getpeername() system
+/// calls. But in this definition we separate these to be more generic.
+/// Also, as a matter of fact our intended usage includes non-connected UDP
+/// communications, in which case at least the remote endpoint should be
+/// provided separately from the socket.
+///
+/// In the actual implementation we represent a socket as a tuple of
+/// socket's file descriptor, address family (e.g. \c AF_INET6),
+/// socket type (e.g. \c SOCK_STREAM), and protocol (e.g. \c IPPROTO_TCP).
+/// The latter three are included in the representation of a socket in order
+/// to provide complete information of how the socket would be created
+/// by the \c socket(2) system call. More specifically in practice, these
+/// parameters could be used to construct a Python socket object from the
+/// file descriptor.
+///
+/// We use the standard \c sockaddr structure to represent endpoints.
+///
+/// Socket session data is an opaque memory region of an arbitrary length
+/// (possibly with some reasonable upper limit).
+///
+/// To forward a socket session between processes, we use connected UNIX
+/// domain sockets established between the processes. The file descriptor
+/// will be forwarded through the sockets as an ancillary data item of
+/// type \c SCM_RIGHTS. Other elements of the session will be transferred
+/// as normal data over the connection.
+///
+/// We provide three classes to help applications forward socket sessions:
+/// \c SocketSessionForwarder is the sender of the UNIX domain connection,
+/// while \c SocketSessionReceiver is the receiver (this interface assumes
+/// one direction of forwarding); \c SocketSession represents a single
+/// socket session.
+///
+/// \c SocketSessionForwarder and \c SocketSessionReceiver objects use a
+/// straightforward protocol to pass elements of socket sessions.
+/// Once the connection is established, the forwarder object first forwards
+/// the file descriptor with 1-byte dummy data. It then forwards a
+/// "(socket) session header", which contains all other elements of the session
+/// except the file descriptor (already forwarded) and session data.
+/// The wire format of the header is as follows:
+/// - The length of the header (16-bit unsigned integer)
+/// - Address family
+/// - Socket type
+/// - Protocol
+/// - Size of the local endpoint in bytes
+/// - Local endpoint (a copy of the memory image of the corresponding
+/// \c sockaddr)
+/// - Size of the remote endpoint in bytes
+/// - Remote endpoint (same as local endpoint)
+/// - Size of session data in bytes
+///
+/// The type of the fields is 32-bit unsigned integer unless explicitly
+/// noted, and all fields are formatted in the network byte order.
+///
+/// The socket session data immediately follows the session header.
+///
+/// Note that the fields do not necessarily be in the network byte order
+/// because they are expected to be exchanged on the same machine. Likewise,
+/// integer elements such as address family do not necessarily be represented
+/// as an fixed-size value (i.e., 32-bit). But fixed size fields are used
+/// in order to ensure maximum portability in such a (rare) case where the
+/// forwarder and the receiver are built with different compilers that have
+/// different definitions of \c int. Also, since \c sockaddr fields are
+/// generally formatted in the network byte order, other fields are defined
+/// so to be consistent.
+///
+/// One basic assumption in the API of this utility is socket sessions should
+/// be forwarded without blocking, thus eliminating the need for incremental
+/// read/write or blocking other important services such as responding to
+/// requests from the application's clients. This assumption should be held
+/// as long as both the forwarder and receiver have sufficient resources
+/// to handle the forwarding process since the communication is local.
+/// But a forward attempt could still block if the receiver is busy (or even
+/// hang up) and cannot keep up with the volume of incoming sessions.
+///
+/// So, in this implementation, the forwarder uses non blocking writes to
+/// forward sessions. If a write attempt could block, it immediately gives
+/// up the operation with an exception. The corresponding application is
+/// expected to catch it, close the connection, and perform any necessary
+/// recovery for that application (that would normally be re-establish the
+/// connection with a new receiver, possibly after confirming the receiving
+/// side is still alive). On the other hand, the receiver implementation
+/// assumes it's possible that it only receive incomplete elements of a
+/// session (such as in the case where the forwarder writes part of the
+/// entire session and gives up the connection). The receiver implementation
+/// throws an exception when it encounters an incomplete session. Like the
+/// case of the forwarder application, the receiver application is expected
+/// to catch it, close the connection, and perform any necessary recovery
+/// steps.
+///
+/// Note that the receiver implementation uses blocking read. So it's
+/// application's responsibility to ensure that there's at least some data
+/// in the connection when the receiver object is requested to receive a
+/// session (unless this operation can be blocking, e.g., by the use of
+/// a separate thread). Also, if the forwarder implementation or application
+/// is malicious or extremely buggy and intentionally sends partial session
+/// and keeps the connection, the receiver could block in receiving a session.
+/// In general, we assume the forwarder doesn't do intentional blocking
+/// as it's a local node and is generally a module of the same (Kea)
+/// system. The minimum requirement for the forwarder implementation (and
+/// application) is to make sure the connection is closed once it detects
+/// an error on it. Even a naive implementation that simply dies due to
+/// the exception will meet this requirement.
+
+/// An exception indicating general errors that takes place in the
+/// socket session related class objects.
+///
+/// In general the errors are unusual but possible failures such as unexpected
+/// connection reset, and suggest the application to close the connection and
+/// (if necessary) reestablish it.
+class SocketSessionError: public Exception {
+public:
+ SocketSessionError(const char *file, size_t line, const char *what):
+ isc::Exception(file, line, what) {}
+};
+
+/// The "base" class of \c SocketSessionForwarder
+///
+/// This class defines abstract interfaces of the \c SocketSessionForwarder
+/// class. Although \c SocketSessionForwarder is not intended to be used in
+/// a polymorphic way, it's not easy to use in tests because it will require
+/// various low level network operations. So it would be useful if we
+/// provide a framework for defining a fake or mock version of it.
+/// An application that needs to use \c SocketSessionForwarder would actually
+/// refer to this base class, and tests for the application would define
+/// and use a fake version of the forwarder class.
+///
+/// Normal applications are not expected to define and use their own derived
+/// version of this base class, while it's not prohibited at the API level.
+///
+/// See description of \c SocketSessionForwarder for the expected interface.
+class BaseSocketSessionForwarder {
+protected:
+ BaseSocketSessionForwarder() {}
+
+public:
+ virtual ~BaseSocketSessionForwarder() {}
+ virtual void connectToReceiver() = 0;
+ virtual void close() = 0;
+ virtual void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len) = 0;
+};
+
+/// The forwarder of socket sessions
+///
+/// An object of this class maintains a UNIX domain socket (normally expected
+/// to be connected to a \c SocketSessionReceiver object) and forwards
+/// socket sessions to the receiver.
+///
+/// See the description of \ref SocketSessionUtility for other details of how
+/// the session forwarding works.
+class SocketSessionForwarder : boost::noncopyable,
+ public BaseSocketSessionForwarder
+{
+public:
+ /// The constructor.
+ ///
+ /// It's constructed with path information of the intended receiver,
+ /// but does not immediately establish a connection to the receiver;
+ /// \c connectToReceiver() must be called to establish it. These are
+ /// separated so that an object of class can be initialized (possibly
+ /// as an attribute of a higher level application class object) without
+ /// knowing the receiver is ready for accepting new forwarders. The
+ /// separate connect interface allows the object to be reused when it
+ /// detects connection failure and tries to re-establish it after closing
+ /// the failed one.
+ ///
+ /// On construction, it also installs a signal filter for SIGPIPE to
+ /// ignore it. Since this class uses a stream-type connected UNIX domain
+ /// socket, if the receiver (abruptly) closes the connection a subsequent
+ /// write operation on the socket would trigger a SIGPIPE signal, which
+ /// kills the caller process by default. This behavior would be
+ /// undesirable in many cases, so this implementation always disables
+ /// the signal.
+ ///
+ /// This approach has some drawbacks, however; first, since signal handling
+ /// is process (or thread) wide, ignoring it may not what the application
+ /// wants. On the other hand, if the application changes how the signal is
+ /// handled after instantiating this class, the new behavior affects the
+ /// class operation. Secondly, even if ignoring the signal is the desired
+ /// operation, it's a waste to set the filter every time this class object
+ /// is constructed. It's sufficient to do it once. We still adopt this
+ /// behavior based on the observation that in most cases applications would
+ /// like to ignore SIGPIPE (or simply doesn't care about it) and that this
+ /// class is not instantiated so often (so the wasteful setting overhead
+ /// should be marginal). On the other hand, doing it every time is
+ /// beneficial if the application is threaded and different threads
+ /// create different forwarder objects (and if signals work per thread).
+ ///
+ /// \exception SocketSessionError \c unix_file is invalid as a path name
+ /// of a UNIX domain socket.
+ /// \exception Unexpected Error in setting a filter for SIGPIPE (see above)
+ /// \exception std::bad_alloc resource allocation failure
+ ///
+ /// \param unix_file Path name of the receiver.
+ explicit SocketSessionForwarder(const std::string& unix_file);
+
+ /// The destructor.
+ ///
+ /// If a connection has been established, it's automatically closed in
+ /// the destructor.
+ virtual ~SocketSessionForwarder();
+
+ /// Establish a connection to the receiver.
+ ///
+ /// This method establishes a connection to the receiver at the path
+ /// given on construction. It makes the underlying UNIX domain socket
+ /// non blocking, so this method (or subsequent \c push() calls) does not
+ /// block.
+ ///
+ /// \exception BadValue The method is called while an already
+ /// established connection is still active.
+ /// \exception SocketSessionError A system error in socket operation.
+ virtual void connectToReceiver();
+
+ /// Close the connection to the receiver.
+ ///
+ /// The connection must have been established by \c connectToReceiver().
+ /// As long as it's met this method is exception free.
+ ///
+ /// \exception BadValue The connection hasn't been established.
+ virtual void close();
+
+ /// Forward a socket session to the receiver.
+ ///
+ /// This method takes a set of parameters that represent a single socket
+ /// session, renders them in the "wire" format according to the internal
+ /// protocol (see \ref SocketSessionUtility) and forwards them to
+ /// the receiver through the UNIX domain connection.
+ ///
+ /// The connection must have been established by \c connectToReceiver().
+ ///
+ /// For simplicity and for the convenience of detecting application
+ /// errors, this method imposes some restrictions on the parameters:
+ /// - Socket family must be either \c AF_INET or \c AF_INET6
+ /// - The address family (\c sa_family) member of the local and remote
+ /// end points must be equal to the \c family parameter
+ /// - Socket session data must not be empty (\c data_len must not be 0
+ /// and \c data must not be NULL)
+ /// - Data length must not exceed 65535
+ /// These are not architectural limitation, and might be loosened in
+ /// future versions as we see the need for flexibility.
+ ///
+ /// Since the underlying UNIX domain socket is non blocking
+ /// (see the description for the constructor), a call to this method
+ /// should either return immediately or result in exception (in case of
+ /// "would block").
+ ///
+ /// \exception BadValue The method is called before establishing a
+ /// connection or given parameters are invalid.
+ /// \exception SocketSessionError A system error in socket operation,
+ /// including the case where the write operation would block.
+ ///
+ /// \param sock The socket file descriptor
+ /// \param family The address family (such as AF_INET6) of the socket
+ /// \param type The socket type (such as SOCK_DGRAM) of the socket
+ /// \param protocol The transport protocol (such as IPPROTO_UDP) of the
+ /// socket
+ /// \param local_end The local end point of the session in the form of
+ /// \c sockaddr.
+ /// \param remote_end The remote end point of the session in the form of
+ /// \c sockaddr.
+ /// \param data A pointer to the beginning of the memory region for the
+ /// session data
+ /// \param data_len The size of the session data in bytes.
+ virtual void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len);
+
+private:
+ struct ForwarderImpl;
+ ForwarderImpl* impl_;
+};
+
+/// Socket session object.
+///
+/// The \c SocketSession class provides a convenient encapsulation
+/// for the notion of a socket session. It's instantiated with straightforward
+/// parameters corresponding to a socket session, and provides read only
+/// accessors to the parameters to ensure data integrity.
+///
+/// In the initial design and implementation it's only used as a return type
+/// of \c SocketSessionReceiver::pop(), but it could also be used by
+/// the \c SocketSessionForwarder class or for other purposes.
+///
+/// It is assumed that the original owner of a \c SocketSession object
+/// (e.g. a class or a function that constructs it) is responsible for validity
+/// of the data passed to the object. See the description of
+/// \c SocketSessionReceiver::pop() for the specific case of that usage.
+class SocketSession {
+public:
+ /// The constructor.
+ ///
+ /// This is a trivial constructor, taking a straightforward representation
+ /// of session parameters and storing them internally to ensure integrity.
+ ///
+ /// As long as the given parameters are valid it never throws an exception.
+ ///
+ /// \exception BadValue Given parameters don't meet the requirement
+ /// (see the parameter descriptions).
+ ///
+ /// \param sock The socket file descriptor
+ /// \param family The address family (such as AF_INET6) of the socket
+ /// \param type The socket type (such as SOCK_DGRAM) of the socket
+ /// \param protocol The transport protocol (such as IPPROTO_UDP) of the
+ /// socket.
+ /// \param local_end The local end point of the session in the form of
+ /// \c sockaddr. Must not be NULL.
+ /// \param remote_end The remote end point of the session in the form of
+ /// \c sockaddr. Must not be NULL.
+ /// \param data A pointer to the beginning of the memory region for the
+ /// session data. Must not be NULL, and the subsequent \c data_len bytes
+ /// must be valid.
+ /// \param data_len The size of the session data in bytes. Must not be 0.
+ SocketSession(int sock, int family, int type, int protocol,
+ const sockaddr* local_end, const sockaddr* remote_end,
+ const void* data, size_t data_len);
+
+ /// Return the socket file descriptor.
+ int getSocket() const { return (sock_); }
+
+ /// Return the address family (such as AF_INET6) of the socket.
+ int getFamily() const { return (family_); }
+
+ /// Return the socket type (such as SOCK_DGRAM) of the socket.
+ int getType() const { return (type_); }
+
+ /// Return the transport protocol (such as IPPROTO_UDP) of the socket.
+ int getProtocol() const { return (protocol_); }
+
+ /// Return the local end point of the session in the form of \c sockaddr.
+ const sockaddr& getLocalEndpoint() const { return (*local_end_); }
+
+ /// Return the remote end point of the session in the form of \c sockaddr.
+ const sockaddr& getRemoteEndpoint() const { return (*remote_end_); }
+
+ /// Return a pointer to the beginning of the memory region for the session
+ /// data.
+ ///
+ /// In the current implementation it should never be NULL, and the region
+ /// of the size returned by \c getDataLength() is expected to be valid.
+ const void* getData() const { return (data_); }
+
+ /// Return the size of the session data in bytes.
+ ///
+ /// In the current implementation it should be always larger than 0.
+ size_t getDataLength() const { return (data_len_); }
+
+private:
+ const int sock_;
+ const int family_;
+ const int type_;
+ const int protocol_;
+ const sockaddr* local_end_;
+ const sockaddr* remote_end_;
+ const void* const data_;
+ const size_t data_len_;
+};
+
+/// The receiver of socket sessions
+///
+/// An object of this class holds a UNIX domain socket for an
+/// <em>established connection</em>, receives socket sessions from
+/// the remote forwarder, and provides the session to the application
+/// in the form of a \c SocketSession object.
+///
+/// Note that this class is instantiated with an already connected socket;
+/// it's not a listening socket that is accepting connection requests from
+/// forwarders. It's application's responsibility to create the listening
+/// socket, listen on it and accept connections. Once the connection is
+/// established, the application would construct a \c SocketSessionReceiver
+/// object with the socket for the newly established connection.
+/// This behavior is based on the design decision that the application should
+/// decide when it performs (possibly) blocking operations (see \ref
+/// SocketSessionUtility for more details).
+///
+/// See the description of \ref SocketSessionUtility for other details of how
+/// the session forwarding works.
+class SocketSessionReceiver : boost::noncopyable {
+public:
+ /// The constructor.
+ ///
+ /// \exception SocketSessionError Any error on an operation that is
+ /// performed on the given socket as part of initialization.
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \param fd A UNIX domain socket for an established connection with
+ /// a forwarder.
+ explicit SocketSessionReceiver(int fd);
+
+ /// The destructor.
+ ///
+ /// The destructor does \c not close the socket given on construction.
+ /// It's up to the application what to do with it (note that the
+ /// application would have to maintain the socket itself for detecting
+ /// the existence of a new socket session asynchronously).
+ ~SocketSessionReceiver();
+
+ /// Receive a socket session from the forwarder.
+ ///
+ /// This method receives wire-format data (see \ref SocketSessionUtility)
+ /// for a socket session on the UNIX domain socket, performs some
+ /// validation on the data, and returns the session information in the
+ /// form of a \c SocketSession object.
+ ///
+ /// The returned SocketSession object is valid only until the next time
+ /// this method is called or until the \c SocketSessionReceiver object is
+ /// destroyed.
+ ///
+ /// The caller is responsible for closing the received socket (whose
+ /// file descriptor is accessible via \c SocketSession::getSocket()).
+ /// If the caller copies the returned \c SocketSession object, it's also
+ /// responsible for making sure the descriptor is closed at most once.
+ /// On the other hand, the caller is not responsible for freeing the
+ /// socket session data (accessible via \c SocketSession::getData());
+ /// the \c SocketSessionReceiver object will clean it up automatically.
+ ///
+ /// It ensures the following:
+ /// - The address family is either \c AF_INET or \c AF_INET6
+ /// - The address family (\c sa_family) member of the local and remote
+ /// end points must be equal to the \c family parameter
+ /// - The socket session data is not empty and does not exceed 65535
+ /// bytes.
+ /// If the validation fails or an unexpected system error happens
+ /// (including a connection close in the meddle of reception), it throws
+ /// an SocketSessionError exception. When this happens, it's very
+ /// unlikely that a subsequent call to this method succeeds, so in reality
+ /// the application is expected to destruct it and close the socket in
+ /// such a case.
+ ///
+ /// \exception SocketSessionError Invalid data is received or a system
+ /// error on socket operation happens.
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \return A \c SocketSession object corresponding to the extracted
+ /// socket session.
+ SocketSession pop();
+
+private:
+ struct ReceiverImpl;
+ ReceiverImpl* impl_;
+};
+
+}
+}
+}
+
+#endif // SOCKETSESSION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/io_utilities.h b/src/lib/util/io_utilities.h
new file mode 100644
index 0000000..bb32819
--- /dev/null
+++ b/src/lib/util/io_utilities.h
@@ -0,0 +1,185 @@
+// Copyright (C) 2011-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef IO_UTILITIES_H
+#define IO_UTILITIES_H
+
+#include <exceptions/exceptions.h>
+#include <cstddef>
+
+namespace isc {
+namespace util {
+
+/// \brief Read Unsigned 16-Bit Integer from Buffer
+///
+/// This is essentially a copy of the isc::util::InputBuffer::readUint16. It
+/// should really be moved into a separate library.
+///
+/// \param buffer Data buffer at least two bytes long of which the first two
+/// bytes are assumed to represent a 16-bit integer in network-byte
+/// order.
+/// \param length Length of the data buffer.
+///
+/// \return Value of 16-bit integer
+inline uint16_t
+readUint16(const void* buffer, size_t length) {
+ if (length < sizeof(uint16_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to read a uint16_t");
+ }
+
+ const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
+
+ uint16_t result = (static_cast<uint16_t>(byte_buffer[0])) << 8;
+ result |= static_cast<uint16_t>(byte_buffer[1]);
+
+ return (result);
+}
+
+/// \brief Write Unsigned 16-Bit Integer to Buffer
+///
+/// This is essentially a copy of isc::util::OutputBuffer::writeUint16. It
+/// should really be moved into a separate library.
+///
+/// \param value 16-bit value to convert
+/// \param buffer Data buffer at least two bytes long into which the 16-bit
+/// value is written in network-byte order.
+/// \param length Length of the data buffer.
+///
+/// \return pointer to the next byte after stored value
+inline uint8_t*
+writeUint16(uint16_t value, void* buffer, size_t length) {
+ if (length < sizeof(uint16_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to write a uint16_t");
+ }
+
+ uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
+
+ byte_buffer[0] = static_cast<uint8_t>((value & 0xff00U) >> 8);
+ byte_buffer[1] = static_cast<uint8_t>(value & 0x00ffU);
+
+ return (byte_buffer + sizeof(uint16_t));
+}
+
+/// \brief Read Unsigned 32-Bit Integer from Buffer
+///
+/// \param buffer Data buffer at least four bytes long of which the first four
+/// bytes are assumed to represent a 32-bit integer in network-byte
+/// order.
+/// \param length Length of the data buffer.
+///
+/// \return Value of 32-bit unsigned integer
+inline uint32_t
+readUint32(const void* buffer, size_t length) {
+ if (length < sizeof(uint32_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to read a uint32_t");
+ }
+
+ const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
+
+ uint32_t result = (static_cast<uint32_t>(byte_buffer[0])) << 24;
+ result |= (static_cast<uint32_t>(byte_buffer[1])) << 16;
+ result |= (static_cast<uint32_t>(byte_buffer[2])) << 8;
+ result |= (static_cast<uint32_t>(byte_buffer[3]));
+
+ return (result);
+}
+
+/// \brief Write Unsigned 32-Bit Integer to Buffer
+///
+/// \param value 32-bit value to convert
+/// \param buffer Data buffer at least four bytes long into which the 32-bit
+/// value is written in network-byte order.
+/// \param length Length of the data buffer.
+///
+/// \return pointer to the next byte after stored value
+inline uint8_t*
+writeUint32(uint32_t value, void* buffer, size_t length) {
+ if (length < sizeof(uint32_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to write a uint32_t");
+ }
+
+ uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
+
+ byte_buffer[0] = static_cast<uint8_t>((value & 0xff000000U) >> 24);
+ byte_buffer[1] = static_cast<uint8_t>((value & 0x00ff0000U) >> 16);
+ byte_buffer[2] = static_cast<uint8_t>((value & 0x0000ff00U) >> 8);
+ byte_buffer[3] = static_cast<uint8_t>((value & 0x000000ffU));
+
+ return (byte_buffer + sizeof(uint32_t));
+}
+
+/// \brief Read Unsigned 64-Bit Integer from Buffer
+///
+/// \param buffer Data buffer at least four bytes long of which the first four
+/// bytes are assumed to represent a 64-bit integer in network-byte
+/// order.
+/// \param length Length of the data buffer.
+///
+/// \return Value of 64-bit unsigned integer
+inline uint64_t
+readUint64(const void* buffer, size_t length) {
+ if (length < sizeof(uint64_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to read a uint64_t");
+ }
+
+ const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
+
+ uint64_t result = (static_cast<uint64_t>(byte_buffer[0])) << 56;
+ result |= (static_cast<uint64_t>(byte_buffer[1])) << 48;
+ result |= (static_cast<uint64_t>(byte_buffer[2])) << 40;
+ result |= (static_cast<uint64_t>(byte_buffer[3])) << 32;
+ result |= (static_cast<uint64_t>(byte_buffer[4])) << 24;
+ result |= (static_cast<uint64_t>(byte_buffer[5])) << 16;
+ result |= (static_cast<uint64_t>(byte_buffer[6])) << 8;
+ result |= (static_cast<uint64_t>(byte_buffer[7]));
+
+ return (result);
+}
+
+/// \brief Write Unsigned 64-Bit Integer to Buffer
+///
+/// \param value 64-bit value to convert
+/// \param buffer Data buffer at least four bytes long into which the 64-bit
+/// value is written in network-byte order.
+/// \param length Length of the data buffer.
+///
+/// \return pointer to the next byte after stored value
+inline uint8_t*
+writeUint64(uint64_t value, void* buffer, size_t length) {
+ if (length < sizeof(uint64_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to write a uint64_t");
+ }
+
+ uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
+
+ byte_buffer[0] = static_cast<uint8_t>((value & 0xff00000000000000UL) >> 56);
+ byte_buffer[1] = static_cast<uint8_t>((value & 0x00ff000000000000UL) >> 48);
+ byte_buffer[2] = static_cast<uint8_t>((value & 0x0000ff0000000000UL) >> 40);
+ byte_buffer[3] = static_cast<uint8_t>((value & 0x000000ff00000000UL) >> 32);
+ byte_buffer[4] = static_cast<uint8_t>((value & 0x00000000ff000000UL) >> 24);
+ byte_buffer[5] = static_cast<uint8_t>((value & 0x0000000000ff0000UL) >> 16);
+ byte_buffer[6] = static_cast<uint8_t>((value & 0x000000000000ff00UL) >> 8);
+ byte_buffer[7] = static_cast<uint8_t>((value & 0x00000000000000ffUL));
+
+ return (byte_buffer + sizeof(uint64_t));
+}
+
+} // namespace util
+} // namespace isc
+
+#endif // IO_UTILITIES_H
diff --git a/src/lib/util/labeled_value.cc b/src/lib/util/labeled_value.cc
new file mode 100644
index 0000000..9fa184a
--- /dev/null
+++ b/src/lib/util/labeled_value.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/labeled_value.h>
+
+namespace isc {
+namespace util {
+
+/**************************** LabeledValue ****************************/
+
+LabeledValue::LabeledValue(const int value, const std::string& label)
+ : value_(value), label_(label) {
+ if (label.empty()) {
+ isc_throw(LabeledValueError, "labels cannot be empty");
+ }
+}
+
+LabeledValue::~LabeledValue(){
+}
+
+int
+LabeledValue::getValue() const {
+ return (value_);
+}
+
+std::string
+LabeledValue::getLabel() const {
+ return (label_);
+}
+
+bool
+LabeledValue::operator==(const LabeledValue& other) const {
+ return (this->value_ == other.value_);
+}
+
+bool
+LabeledValue::operator!=(const LabeledValue& other) const {
+ return (this->value_ != other.value_);
+}
+
+bool
+LabeledValue::operator<(const LabeledValue& other) const {
+ return (this->value_ < other.value_);
+}
+
+std::ostream& operator<<(std::ostream& os, const LabeledValue& vlp) {
+ os << vlp.getLabel();
+ return (os);
+}
+
+/**************************** LabeledValueSet ****************************/
+
+const char* LabeledValueSet::UNDEFINED_LABEL = "UNDEFINED";
+
+LabeledValueSet::LabeledValueSet(){
+}
+
+LabeledValueSet::~LabeledValueSet() {
+}
+
+void
+LabeledValueSet::add(LabeledValuePtr entry) {
+ if (!entry) {
+ isc_throw(LabeledValueError, "cannot add an null entry to set");
+ }
+
+ const int value = entry->getValue();
+ if (isDefined(value)) {
+ isc_throw(LabeledValueError,
+ "value: " << value << " is already defined as: "
+ << getLabel(value));
+ }
+
+ map_[value] = entry;
+}
+
+void
+LabeledValueSet::add(const int value, const std::string& label) {
+ add(LabeledValuePtr(new LabeledValue(value,label)));
+}
+
+const LabeledValuePtr&
+LabeledValueSet::get(int value) {
+ static LabeledValuePtr undefined;
+ LabeledValueMap::iterator it = map_.find(value);
+ if (it != map_.end()) {
+ return ((*it).second);
+ }
+
+ // Return an empty pointer when not found.
+ return (undefined);
+}
+
+bool
+LabeledValueSet::isDefined(const int value) const {
+ LabeledValueMap::const_iterator it = map_.find(value);
+ return (it != map_.end());
+}
+
+std::string
+LabeledValueSet::getLabel(const int value) const {
+ LabeledValueMap::const_iterator it = map_.find(value);
+ if (it != map_.end()) {
+ const LabeledValuePtr& ptr = (*it).second;
+ return (ptr->getLabel());
+ }
+
+ return (std::string(UNDEFINED_LABEL));
+}
+
+} // namespace isc::util
+} // namespace isc
diff --git a/src/lib/util/labeled_value.h b/src/lib/util/labeled_value.h
new file mode 100644
index 0000000..e85b537
--- /dev/null
+++ b/src/lib/util/labeled_value.h
@@ -0,0 +1,176 @@
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef LABELED_VALUE_H
+#define LABELED_VALUE_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+#include <ostream>
+#include <string>
+#include <map>
+
+/// @file labeled_value.h This file defines classes: LabeledValue and
+/// LabeledValueSet.
+
+namespace isc {
+namespace util {
+
+/// @brief Thrown if an error is encountered handling a LabeledValue.
+class LabeledValueError : public isc::Exception {
+public:
+ LabeledValueError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Implements the concept of a constant value with a text label.
+///
+/// This class implements an association between a constant integer value
+/// and a text label. It provides a single constructor, accessors for both
+/// the value and label, and boolean operators which treat the value as
+/// the "key" for comparisons. This allows them to be assembled into
+/// dictionaries of unique values. Note, that the labels are not required to
+/// be unique but in practice it makes little sense to for them to be
+/// otherwise.
+class LabeledValue {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param value the numeric constant value to be labeled.
+ /// @param label the text label to associate to this value.
+ ///
+ /// @throw LabeledValueError if label is empty.
+ LabeledValue(const int value, const std::string& label);
+
+ /// @brief Destructor.
+ ///
+ /// Destructor is virtual to permit derivations.
+ virtual ~LabeledValue();
+
+ /// @brief Gets the integer value of this instance.
+ ///
+ /// @return integer value of this instance.
+ int getValue() const;
+
+ /// @brief Gets the text label of this instance.
+ ///
+ /// @return The text label as string
+ std::string getLabel() const;
+
+ /// @brief Equality operator
+ ///
+ /// @return True if a.value_ is equal to b.value_.
+ bool operator==(const LabeledValue& other) const;
+
+ /// @brief Inequality operator
+ ///
+ /// @return True if a.value_ is not equal to b.value_.
+ bool operator!=(const LabeledValue& other) const;
+
+ /// @brief Less-than operator
+ ///
+ /// @return True if a.value_ is less than b.value_.
+ bool operator<(const LabeledValue& other) const;
+
+private:
+ /// @brief The numeric value to label.
+ int value_;
+
+ /// @brief The text label for the value.
+ std::string label_;
+};
+
+/// @brief Dumps the label to ostream.
+std::ostream& operator<<(std::ostream& os, const LabeledValue& vlp);
+
+/// @brief Defines a shared pointer to a LabeledValue instance.
+typedef boost::shared_ptr<LabeledValue> LabeledValuePtr;
+
+/// @brief Defines a map of pointers to LabeledValues keyed by value.
+typedef std::map<unsigned int, LabeledValuePtr> LabeledValueMap;
+
+
+/// @brief Implements a set of unique LabeledValues.
+///
+/// This class is intended to function as a dictionary of numeric values
+/// and the labels associated with them. It is essentially a thin wrapper
+/// around a std::map of LabeledValues, keyed by their values. This is handy
+/// for defining a set of "valid" constants while conveniently associating a
+/// text label with each value.
+///
+/// This class offers two variants of an add method for adding entries to the
+/// set, and accessors for finding an entry or an entry's label by value.
+/// Note that the add methods may throw but all accessors are exception safe.
+/// It is up to the caller to determine when and if an undefined value is
+/// exception-worthy.
+///
+/// More interestingly, a derivation of this class can be used to "define"
+/// valid instances of derivations of LabeledValue.
+class LabeledValueSet {
+public:
+ /// @brief Defines a text label returned by when value is not found.
+ static const char* UNDEFINED_LABEL;
+
+ /// @brief Constructor
+ ///
+ /// Constructs an empty set.
+ LabeledValueSet();
+
+ /// @brief Destructor
+ ///
+ /// Destructor is virtual to permit derivations.
+ virtual ~LabeledValueSet();
+
+ /// @brief Adds the given entry to the set
+ ///
+ /// @param entry is the entry to add.
+ ///
+ /// @throw LabeledValuePtr if the entry is null or the set already
+ /// contains an entry with the same value.
+ void add(LabeledValuePtr entry);
+
+ /// @brief Adds an entry to the set for the given value and label
+ ///
+ /// @param value the numeric constant value to be labeled.
+ /// @param label the text label to associate to this value.
+ ///
+ /// @throw LabeledValuePtr if the label is empty, or if the set
+ /// already contains an entry with the same value.
+ void add(const int value, const std::string& label);
+
+ /// @brief Fetches a pointer to the entry associated with value
+ ///
+ /// @param value is the value of the entry desired.
+ ///
+ /// @return A pointer to the entry if the entry was found otherwise the
+ /// pointer is empty.
+ const LabeledValuePtr& get(int value);
+
+ /// @brief Tests if the set contains an entry for the given value.
+ ///
+ /// @param value is the value of the entry to test.
+ ///
+ /// @return True if an entry for value exists in the set, false if not.
+ bool isDefined(const int value) const;
+
+ /// @brief Fetches the label for the given value
+ ///
+ /// @param value is the value for which the label is desired.
+ ///
+ /// @return the label of the value if defined, otherwise it returns
+ /// UNDEFINED_LABEL.
+ std::string getLabel(const int value) const;
+
+private:
+ /// @brief The map of labeled values.
+ LabeledValueMap map_;
+};
+
+} // namespace isc::util
+} // namespace isc
+#endif
diff --git a/src/lib/util/memory_segment.h b/src/lib/util/memory_segment.h
new file mode 100644
index 0000000..c9ae97f
--- /dev/null
+++ b/src/lib/util/memory_segment.h
@@ -0,0 +1,333 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MEMORY_SEGMENT_H
+#define MEMORY_SEGMENT_H
+
+#include <exceptions/exceptions.h>
+
+#include <utility>
+
+#include <stdlib.h>
+
+namespace isc {
+namespace util {
+
+/// \brief Exception that can be thrown when constructing a MemorySegment
+/// object.
+class MemorySegmentOpenError : public Exception {
+public:
+ MemorySegmentOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief Exception that is thrown, when allocating space in a MemorySegment
+/// results in growing the underlying segment.
+///
+/// See MemorySegment::allocate() for details.
+class MemorySegmentGrown : public Exception {
+public:
+ MemorySegmentGrown(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief General error that can be thrown by a MemorySegment
+/// implementation.
+class MemorySegmentError : public Exception {
+public:
+ MemorySegmentError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief Memory Segment Class
+///
+/// This class specifies an interface for allocating memory segments.
+/// It's intended to provide a unified interface, whether the underlying
+/// memory is local to a specific process or is sharable by multiple
+/// processes.
+///
+/// This is an abstract class and a real implementation such as
+/// MemorySegmentLocal should be used in code.
+class MemorySegment {
+public:
+ /// \brief Destructor
+ virtual ~MemorySegment() {}
+
+ /// \brief Allocate/acquire a fragment of memory.
+ ///
+ /// The source of the memory is dependent on the implementation used.
+ ///
+ /// Depending on the implementation details, it may have to grow the
+ /// internal memory segment (again, in an implementation dependent way)
+ /// to allocate the required size of memory. In that case the
+ /// implementation must grow the internal segment sufficiently so the
+ /// next call to allocate() for the same size will succeed, and throw
+ /// a \c MemorySegmentGrown exception (not really allocating the memory
+ /// yet).
+ ///
+ /// An application that uses this memory segment abstraction to allocate
+ /// memory should expect this exception, and should normally catch it
+ /// at an appropriate layer (which may be immediately after a call to
+ /// \c allocate() or a bit higher layer). It should interpret the
+ /// exception as any raw address that belongs to the segment may have
+ /// been remapped and must be re-fetched via an already established
+ /// named address using the \c getNamedAddress() method.
+ ///
+ /// The intended use case of \c allocate() with the \c MemorySegmentGrown
+ /// exception is to build a complex object that would internally require
+ /// multiple calls to \c allocate():
+ ///
+ /// \code
+ /// ComplicatedStuff* stuff = NULL;
+ /// while (!stuff) { // this must eventually succeed or result in bad_alloc
+ /// try {
+ /// // create() is a factory method that takes a memory segment
+ /// // and calls allocate() on it multiple times. create()
+ /// // provides an exception guarantee that any intermediately
+ /// // allocated memory will be properly deallocate()-ed on
+ /// // exception.
+ /// stuff = ComplicatedStuff::create(mem_segment);
+ /// } catch (const MemorySegmentGrown&) { /* just try again */ }
+ /// }
+ /// \endcode
+ ///
+ /// This way, \c create() can be written as if each call to \c allocate()
+ /// always succeeds.
+ ///
+ /// Alternatively, or in addition to this, we could introduce a "no throw"
+ /// version of this method with a way to tell the caller the reason of
+ /// any failure (whether it's really out of memory or just due to growing
+ /// the segment). That would be more convenient if the caller wants to
+ /// deal with the failures on a per-call basis rather than as a set
+ /// of calls like in the above example. At the moment, we don't expect
+ /// to have such use-cases, so we only provide the exception
+ /// version.
+ ///
+ /// \throw std::bad_alloc The implementation cannot allocate the
+ /// requested storage.
+ /// \throw MemorySegmentGrown The memory segment doesn't have sufficient
+ /// space for the requested size and has grown internally.
+ /// \throw MemorySegmentError An attempt was made to allocate
+ /// storage on a read-only memory segment.
+ ///
+ /// \param size The size of the memory requested in bytes.
+ /// \return Returns pointer to the memory allocated.
+ virtual void* allocate(size_t size) = 0;
+
+ /// \brief Free/release a segment of memory.
+ ///
+ /// This method may throw <code>isc::OutOfRange</code> if \c size is
+ /// not equal to the originally allocated size. \c size could be
+ /// used by some implementations such as a slice allocator, where
+ /// freeing memory also requires the size to be specified. We also
+ /// use this argument in some implementations to test if all allocated
+ /// memory was deallocated properly.
+ ///
+ /// Specific implementation may also throw \c MemorySegmentError if it
+ /// encounters violation of implementation specific restrictions.
+ ///
+ /// In general, however, this method must succeed and exception free
+ /// as long as the caller passes valid parameters (\c ptr specifies
+ /// memory previously allocated and \c size is correct).
+ ///
+ /// \throw OutOfRange The passed size doesn't match the allocated memory
+ /// size (when identifiable for the implementation).
+ /// \throw MemorySegmentError Failure of implementation specific
+ /// validation.
+ ///
+ /// \param ptr Pointer to the block of memory to free/release. This
+ /// should be equal to a value returned by <code>allocate()</code>.
+ /// \param size The size of the memory to be freed in bytes. This
+ /// should be equal to the number of bytes originally allocated.
+ virtual void deallocate(void* ptr, size_t size) = 0;
+
+ /// \brief Check if all allocated memory was deallocated.
+ ///
+ /// \return Returns <code>true</code> if all allocated memory (including
+ /// names associated by memory addresses by \c setNamedAddress()) was
+ /// deallocated, <code>false</code> otherwise.
+ virtual bool allMemoryDeallocated() const = 0;
+
+ /// \brief Associate specified address in the segment with a given name.
+ ///
+ /// This method establishes an association between the given name and
+ /// the address in an implementation specific way. The stored address
+ /// is retrieved by the name later by calling \c getNamedAddress().
+ /// If the underlying memory segment is sharable by multiple processes,
+ /// the implementation must ensure the portability of the association;
+ /// if a process gives an address in the shared segment a name, another
+ /// process that shares the same segment should be able to retrieve the
+ /// corresponding address by that name (in such cases the real address
+ /// may be different between these two processes).
+ ///
+ /// Some names are reserved for internal use by this class. If such
+ /// a name is passed to this method, an \c isc::InvalidParameter
+ /// exception will be thrown. See \c validateName() method for details.
+ ///
+ /// \c addr must be 0 (NULL) or an address that belongs to this segment.
+ /// The latter case means it must be the return value of a previous call
+ /// to \c allocate(). The actual implementation is encouraged to detect
+ /// violation of this restriction and signal it with an exception, but
+ /// it's not an API requirement. It's generally the caller's
+ /// responsibility to meet the restriction. Note that NULL is allowed
+ /// as \c addr even if it wouldn't be considered to "belong to" the
+ /// segment in its normal sense; it can be used to indicate that memory
+ /// has not been allocated for the specified name. A subsequent call
+ /// to \c getNamedAddress() will return NamedAddressResult(true, NULL)
+ /// for that name.
+ ///
+ /// \note Naming an address is intentionally separated from allocation
+ /// so that, for example, one module of a program can name a memory
+ /// region allocated by another module of the program.
+ ///
+ /// There can be an existing association for the name; in that case the
+ /// association will be overridden with the newly given address.
+ ///
+ /// While normally unexpected, it's possible that available space in the
+ /// segment is not sufficient to allocate a space (if not already exist)
+ /// for the specified name in the segment. In that case, if possible, the
+ /// implementation should try to grow the internal segment and retry
+ /// establishing the association. The implementation should throw
+ /// std::bad_alloc if even reasonable attempts of retry still fail.
+ ///
+ /// This method should normally return false, but if the internal segment
+ /// had to grow to store the given name, it must return true. The
+ /// application should interpret it just like the case of
+ /// \c MemorySegmentGrown exception thrown from the \c allocate() method.
+ ///
+ /// \note The behavior in case the internal segment grows is different
+ /// from that of \c allocate(). This is intentional. In intended use
+ /// cases (for the moment) this method will be called independently,
+ /// rather than as part of a set of allocations. It's also expected
+ /// that other raw memory addresses (which would have been invalidated
+ /// due to the change to the segment) won't be referenced directly
+ /// immediately after this call. So, the caller should normally be able
+ /// to call this method as mostly never-fail one (except in case of real
+ /// memory exhaustion) and ignore the return value.
+ ///
+ /// \throw std::bad_alloc Allocation of a segment space for the given name
+ /// failed.
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ /// \throw MemorySegmentError Failure of implementation specific
+ /// validation.
+ ///
+ /// \param name A C string to be associated with \c addr. Must not be NULL.
+ /// \param addr A memory address returned by a prior call to \c allocate.
+ /// \return true if the internal segment has grown to allocate space for
+ /// the name; false otherwise (see above).
+ bool setNamedAddress(const char* name, void* addr) {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ validateName(name);
+ return (setNamedAddressImpl(name, addr));
+ }
+
+ /// \brief Type definition for result returned by getNamedAddress()
+ typedef std::pair<bool, void*> NamedAddressResult;
+
+ /// \brief Return the address in the segment that has the given name.
+ ///
+ /// This method returns the memory address in the segment corresponding
+ /// to the specified \c name. The name and address must have been
+ /// associated by a prior call to \c setNameAddress(). If no address
+ /// associated with the given name is found, it returns NULL.
+ ///
+ /// Some names are reserved for internal use by this class. If such
+ /// a name is passed to this method, an \c isc::InvalidParameter
+ /// exception will be thrown. See \c validateName() method for details.
+ ///
+ /// This method should generally be considered exception free, but there
+ /// can be a small chance it throws, depending on the internal
+ /// implementation (e.g., if it converts the name to std::string), so the
+ /// API doesn't guarantee that property. In general, if this method
+ /// throws it should be considered a fatal condition.
+ ///
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ ///
+ /// \param name A C string of which the segment memory address is to be
+ /// returned. Must not be NULL.
+ /// \return An std::pair containing a bool (set to true if the name
+ /// was found, or false otherwise) and the address associated with
+ /// the name (which is undefined if the name was not found).
+ NamedAddressResult getNamedAddress(const char* name) const {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ validateName(name);
+ return (getNamedAddressImpl(name));
+ }
+
+ /// \brief Delete a name previously associated with a segment address.
+ ///
+ /// This method deletes the association of the given \c name to
+ /// a corresponding segment address previously established by
+ /// \c setNamedAddress(). If there is no association for the given name
+ /// this method returns false; otherwise it returns true.
+ ///
+ /// Some names are reserved for internal use by this class. If such
+ /// a name is passed to this method, an \c isc::InvalidParameter
+ /// exception will be thrown. See \c validateName() method for details.
+ ///
+ /// See \c getNamedAddress() about exception consideration.
+ ///
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ /// \throw MemorySegmentError Failure of implementation specific
+ /// validation.
+ ///
+ /// \param name A C string of which the segment memory address is to be
+ /// deleted. Must not be NULL.
+ bool clearNamedAddress(const char* name) {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ validateName(name);
+ return (clearNamedAddressImpl(name));
+ }
+
+private:
+ /// \brief Validate the passed name.
+ ///
+ /// This method validates the passed name (for name/address pairs)
+ /// and throws \c InvalidParameter if the name fails
+ /// validation. Otherwise, it does nothing.
+ ///
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ static void validateName(const char* name) {
+ if (!name) {
+ isc_throw(InvalidParameter, "NULL is invalid for a name.");
+ } else if (*name == '\0') {
+ isc_throw(InvalidParameter, "Empty names are invalid.");
+ } else if (*name == '_') {
+ isc_throw(InvalidParameter,
+ "Names beginning with '_' are reserved for "
+ "internal use only.");
+ }
+ }
+
+protected:
+ /// \brief Implementation of setNamedAddress beyond common validation.
+ virtual bool setNamedAddressImpl(const char* name, void* addr) = 0;
+
+ /// \brief Implementation of getNamedAddress beyond common validation.
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const = 0;
+
+ /// \brief Implementation of clearNamedAddress beyond common validation.
+ virtual bool clearNamedAddressImpl(const char* name) = 0;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // MEMORY_SEGMENT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/memory_segment_local.cc b/src/lib/util/memory_segment_local.cc
new file mode 100644
index 0000000..2331e4b
--- /dev/null
+++ b/src/lib/util/memory_segment_local.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/memory_segment_local.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace util {
+
+void*
+MemorySegmentLocal::allocate(size_t size) {
+ void* ptr = malloc(size);
+ if (ptr == NULL) {
+ throw std::bad_alloc();
+ }
+
+ allocated_size_ += size;
+ return (ptr);
+}
+
+void
+MemorySegmentLocal::deallocate(void* ptr, size_t size) {
+ if (ptr == NULL) {
+ // Return early if NULL is passed to be deallocated (without
+ // modifying allocated_size, or comparing against it).
+ return;
+ }
+
+ if (size > allocated_size_) {
+ isc_throw(OutOfRange, "Invalid size to deallocate: " << size
+ << "; currently allocated size: " << allocated_size_);
+ }
+
+ allocated_size_ -= size;
+ free(ptr);
+}
+
+bool
+MemorySegmentLocal::allMemoryDeallocated() const {
+ return (allocated_size_ == 0 && named_addrs_.empty());
+}
+
+MemorySegment::NamedAddressResult
+MemorySegmentLocal::getNamedAddressImpl(const char* name) const {
+ std::map<std::string, void*>::const_iterator found =
+ named_addrs_.find(name);
+ if (found != named_addrs_.end()) {
+ return (NamedAddressResult(true, found->second));
+ }
+ return (NamedAddressResult(false, static_cast<void*>(0)));
+}
+
+bool
+MemorySegmentLocal::setNamedAddressImpl(const char* name, void* addr) {
+ named_addrs_[name] = addr;
+ return (false);
+}
+
+bool
+MemorySegmentLocal::clearNamedAddressImpl(const char* name) {
+ const size_t n_erased = named_addrs_.erase(name);
+ return (n_erased != 0);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/memory_segment_local.h b/src/lib/util/memory_segment_local.h
new file mode 100644
index 0000000..2c0ee53
--- /dev/null
+++ b/src/lib/util/memory_segment_local.h
@@ -0,0 +1,100 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MEMORY_SEGMENT_LOCAL_H
+#define MEMORY_SEGMENT_LOCAL_H
+
+#include <util/memory_segment.h>
+
+#include <string>
+#include <map>
+
+namespace isc {
+namespace util {
+
+/// \brief malloc/free based Memory Segment class
+///
+/// This class specifies a concrete implementation for a malloc/free
+/// based MemorySegment. Please see the MemorySegment class
+/// documentation for usage.
+class MemorySegmentLocal : public MemorySegment {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a local memory segment object
+ MemorySegmentLocal() : allocated_size_(0) {
+ }
+
+ /// \brief Destructor
+ virtual ~MemorySegmentLocal() {}
+
+ /// \brief Allocate/acquire a segment of memory. The source of the
+ /// memory is libc's malloc().
+ ///
+ /// Throws <code>std::bad_alloc</code> if the implementation cannot
+ /// allocate the requested storage.
+ ///
+ /// \param size The size of the memory requested in bytes.
+ /// \return Returns pointer to the memory allocated.
+ virtual void* allocate(size_t size);
+
+ /// \brief Free/release a segment of memory.
+ ///
+ /// This method may throw <code>isc::OutOfRange</code> if \c size is
+ /// not equal to the originally allocated size.
+ ///
+ /// \param ptr Pointer to the block of memory to free/release. This
+ /// should be equal to a value returned by <code>allocate()</code>.
+ /// \param size The size of the memory to be freed in bytes. This
+ /// should be equal to the number of bytes originally allocated.
+ virtual void deallocate(void* ptr, size_t size);
+
+ /// \brief Check if all allocated memory was deallocated.
+ ///
+ /// \return Returns <code>true</code> if all allocated memory was
+ /// deallocated, <code>false</code> otherwise.
+ virtual bool allMemoryDeallocated() const;
+
+ /// \brief Local segment version of getNamedAddress.
+ ///
+ /// There's a small chance this method could throw std::bad_alloc.
+ /// It should be considered a fatal error.
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const;
+
+ /// \brief Local segment version of setNamedAddress.
+ ///
+ /// This version does not validate the given address to see whether it
+ /// belongs to this segment.
+ ///
+ /// This implementation of this method always returns \c false (but the
+ /// application should expect a return value of \c true unless it knows
+ /// the memory segment class is \c MemorySegmentLocal and needs to
+ /// exploit the fact).
+ virtual bool setNamedAddressImpl(const char* name, void* addr);
+
+ /// \brief Local segment version of clearNamedAddress.
+ ///
+ /// There's a small chance this method could throw std::bad_alloc.
+ /// It should be considered a fatal error.
+ virtual bool clearNamedAddressImpl(const char* name);
+
+private:
+ // allocated_size_ can underflow, wrap around to max size_t (which
+ // is unsigned). But because we only do a check against 0 and not a
+ // relation comparison, this is okay.
+ size_t allocated_size_;
+
+ std::map<std::string, void*> named_addrs_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // MEMORY_SEGMENT_LOCAL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/multi_threading_mgr.cc b/src/lib/util/multi_threading_mgr.cc
new file mode 100644
index 0000000..311b0ab
--- /dev/null
+++ b/src/lib/util/multi_threading_mgr.cc
@@ -0,0 +1,291 @@
+// Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/multi_threading_mgr.h>
+
+namespace isc {
+namespace util {
+
+MultiThreadingMgr::MultiThreadingMgr()
+ : enabled_(false), critical_section_count_(0), thread_pool_size_(0) {
+}
+
+MultiThreadingMgr::~MultiThreadingMgr() {
+}
+
+MultiThreadingMgr&
+MultiThreadingMgr::instance() {
+ static MultiThreadingMgr manager;
+ return (manager);
+}
+
+bool
+MultiThreadingMgr::getMode() const {
+ return (enabled_);
+}
+
+void
+MultiThreadingMgr::setMode(bool enabled) {
+ enabled_ = enabled;
+}
+
+void
+MultiThreadingMgr::enterCriticalSection() {
+ checkCallbacksPermissions();
+ bool inside = isInCriticalSection();
+ // Increment the counter to allow CS to be created in the registered
+ // callbacks (in which case the new CS would not call callbacks again).
+ // The counter must be updated regardless of the MT mode because the MT mode
+ // can change between the constructor call and the destructor call.
+ ++critical_section_count_;
+ if (getMode() && !inside) {
+ if (getThreadPoolSize()) {
+ thread_pool_.stop();
+ }
+ // Now it is safe to call callbacks which can also create other CSs.
+ callEntryCallbacks();
+ }
+}
+
+void
+MultiThreadingMgr::exitCriticalSection() {
+ // The number of CS destructors should match the number of CS constructors.
+ // The case when counter is 0 is only possible if calling this function
+ // explicitly, which is a programming error.
+ if (!isInCriticalSection()) {
+ isc_throw(InvalidOperation, "invalid value for critical section count");
+ }
+ // Decrement the counter to allow the check for last CS destructor which
+ // would result in restarting the thread pool.
+ // The counter must be updated regardless of the MT mode because the MT mode
+ // can change between the constructor call and the destructor call.
+ --critical_section_count_;
+ if (getMode() && !isInCriticalSection()) {
+ if (getThreadPoolSize()) {
+ thread_pool_.start(getThreadPoolSize());
+ }
+ // Now it is safe to call callbacks which can also create other CSs.
+ callExitCallbacks();
+ }
+}
+
+bool
+MultiThreadingMgr::isInCriticalSection() {
+ return (critical_section_count_ != 0);
+}
+
+ThreadPool<std::function<void()>>&
+MultiThreadingMgr::getThreadPool() {
+ return thread_pool_;
+}
+
+uint32_t
+MultiThreadingMgr::getThreadPoolSize() const {
+ return (thread_pool_size_);
+}
+
+void
+MultiThreadingMgr::setThreadPoolSize(uint32_t size) {
+ thread_pool_size_ = size;
+}
+
+uint32_t
+MultiThreadingMgr::getPacketQueueSize() {
+ return (thread_pool_.getMaxQueueSize());
+}
+
+void
+MultiThreadingMgr::setPacketQueueSize(uint32_t size) {
+ thread_pool_.setMaxQueueSize(size);
+}
+
+uint32_t
+MultiThreadingMgr::detectThreadCount() {
+ return (std::thread::hardware_concurrency());
+}
+
+void
+MultiThreadingMgr::apply(bool enabled, uint32_t thread_count, uint32_t queue_size) {
+ // check the enabled flag
+ if (enabled) {
+ // check for auto scaling (enabled flag true but thread_count 0)
+ if (!thread_count) {
+ // might also return 0
+ thread_count = MultiThreadingMgr::detectThreadCount();
+ }
+ } else {
+ thread_count = 0;
+ queue_size = 0;
+ }
+ // check enabled flag and explicit number of threads or system supports
+ // hardware concurrency
+ if (thread_count) {
+ if (thread_pool_.size()) {
+ thread_pool_.stop();
+ }
+ setThreadPoolSize(thread_count);
+ setPacketQueueSize(queue_size);
+ setMode(true);
+ if (!isInCriticalSection()) {
+ thread_pool_.start(thread_count);
+ }
+ } else {
+ removeAllCriticalSectionCallbacks();
+ thread_pool_.reset();
+ setMode(false);
+ setThreadPoolSize(thread_count);
+ setPacketQueueSize(queue_size);
+ }
+}
+
+void
+MultiThreadingMgr::checkCallbacksPermissions() {
+ if (getMode()) {
+ for (const auto& cb : cs_callbacks_.getCallbackSets()) {
+ try {
+ (cb.check_cb_)();
+ } catch (const isc::MultiThreadingInvalidOperation& ex) {
+ // If any registered callback throws, the exception needs to be
+ // propagated to the caller of the
+ // @ref MultiThreadingCriticalSection constructor.
+ // Because this function is called by the
+ // @ref MultiThreadingCriticalSection constructor, throwing here
+ // is safe.
+ throw;
+ } catch (...) {
+ // We can't log it and throwing could be chaos.
+ // We'll swallow it and tell people their callbacks
+ // must be exception-proof
+ }
+ }
+ }
+}
+
+void
+MultiThreadingMgr::callEntryCallbacks() {
+ if (getMode()) {
+ const auto& callbacks = cs_callbacks_.getCallbackSets();
+ for (auto cb_it = callbacks.begin(); cb_it != callbacks.end(); cb_it++) {
+ try {
+ (cb_it->entry_cb_)();
+ } catch (...) {
+ // We can't log it and throwing could be chaos.
+ // We'll swallow it and tell people their callbacks
+ // must be exception-proof
+ }
+ }
+ }
+}
+
+void
+MultiThreadingMgr::callExitCallbacks() {
+ if (getMode()) {
+ const auto& callbacks = cs_callbacks_.getCallbackSets();
+ for (auto cb_it = callbacks.rbegin(); cb_it != callbacks.rend(); cb_it++) {
+ try {
+ (cb_it->exit_cb_)();
+ } catch (...) {
+ // We can't log it and throwing could be chaos.
+ // We'll swallow it and tell people their callbacks
+ // must be exception-proof
+ // Because this function is called by the
+ // @ref MultiThreadingCriticalSection destructor, throwing here
+ // is not safe and will cause the process to crash.
+ }
+ }
+ }
+}
+
+void
+MultiThreadingMgr::addCriticalSectionCallbacks(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb) {
+ cs_callbacks_.addCallbackSet(name, check_cb, entry_cb, exit_cb);
+}
+
+void
+MultiThreadingMgr::removeCriticalSectionCallbacks(const std::string& name) {
+ cs_callbacks_.removeCallbackSet(name);
+}
+
+void
+MultiThreadingMgr::removeAllCriticalSectionCallbacks() {
+ cs_callbacks_.removeAll();
+}
+
+MultiThreadingCriticalSection::MultiThreadingCriticalSection() {
+ MultiThreadingMgr::instance().enterCriticalSection();
+}
+
+MultiThreadingCriticalSection::~MultiThreadingCriticalSection() {
+ MultiThreadingMgr::instance().exitCriticalSection();
+}
+
+MultiThreadingLock::MultiThreadingLock(std::mutex& mutex) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_ = std::unique_lock<std::mutex>(mutex);
+ }
+}
+
+void
+CSCallbackSetList::addCallbackSet(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb) {
+ if (name.empty()) {
+ isc_throw(BadValue, "CSCallbackSetList - name cannot be empty");
+ }
+
+ if (!check_cb) {
+ isc_throw(BadValue, "CSCallbackSetList - check callback for " << name
+ << " cannot be empty");
+ }
+
+ if (!entry_cb) {
+ isc_throw(BadValue, "CSCallbackSetList - entry callback for " << name
+ << " cannot be empty");
+ }
+
+ if (!exit_cb) {
+ isc_throw(BadValue, "CSCallbackSetList - exit callback for " << name
+ << " cannot be empty");
+ }
+
+ for (auto const& callback : cb_sets_) {
+ if (callback.name_ == name) {
+ isc_throw(BadValue, "CSCallbackSetList - callbacks for " << name
+ << " already exist");
+ }
+ }
+
+ cb_sets_.push_back(CSCallbackSet(name, check_cb, entry_cb, exit_cb));
+}
+
+void
+CSCallbackSetList::removeCallbackSet(const std::string& name) {
+ for (auto it = cb_sets_.begin(); it != cb_sets_.end(); ++it) {
+ if ((*it).name_ == name) {
+ cb_sets_.erase(it);
+ break;
+ }
+ }
+}
+
+void
+CSCallbackSetList::removeAll() {
+ cb_sets_.clear();
+}
+
+const std::list<CSCallbackSet>&
+CSCallbackSetList::getCallbackSets() {
+ return (cb_sets_);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/multi_threading_mgr.h b/src/lib/util/multi_threading_mgr.h
new file mode 100644
index 0000000..e86c488
--- /dev/null
+++ b/src/lib/util/multi_threading_mgr.h
@@ -0,0 +1,374 @@
+// Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MULTI_THREADING_MGR_H
+#define MULTI_THREADING_MGR_H
+
+#include <util/thread_pool.h>
+#include <functional>
+#include <list>
+
+#include <boost/noncopyable.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+
+
+/// @brief Embodies a named set of CriticalSection callbacks.
+///
+/// This class associates with a name, a set of callbacks, one to be invoked
+/// before CriticalSection entry and exit callbacks to check current thread
+/// permissions to perform such actions, one to be invoked upon CriticalSection
+/// entry and one to be invoked upon CriticalSection exit.
+/// The name allows the set to be uniquely identified such that they can be
+/// added and removed as needed.
+/// The check current thread permissions callback needs to throw
+/// @ref MultiThreadingInvalidOperation if the thread is not allowed to perform
+/// CriticalSection entry and exit. Any other exception thrown by the check
+/// permission callbacks will be silently ignored.
+/// The CriticalSection entry and exit callbacks exceptions will be silently
+/// ignored.
+struct CSCallbackSet {
+ /// @brief Defines a callback as a simple void() functor.
+ typedef std::function<void()> Callback;
+
+ /// @brief Constructor
+ ///
+ /// @param name Name by which the callbacks can be found.
+ /// @param check_cb Callback to check current thread permissions to call
+ /// the CriticalSection entry and exit callbacks.
+ /// @param entry_cb Callback to invoke upon CriticalSection entry.
+ /// @param exit_cb Callback to invoke upon CriticalSection exit.
+ CSCallbackSet(const std::string& name, const Callback& check_cb,
+ const Callback& entry_cb, const Callback& exit_cb)
+ : name_(name), check_cb_(check_cb), entry_cb_(entry_cb),
+ exit_cb_(exit_cb) {}
+
+ /// @brief Name by which the callback can be found.
+ std::string name_;
+
+ /// @brief Check permissions callback associated with name.
+ Callback check_cb_;
+
+ /// @brief Entry point callback associated with name.
+ Callback entry_cb_;
+
+ /// @brief Exit point callback associated with name.
+ Callback exit_cb_;
+};
+
+/// @brief Maintains list of unique CSCallbackSets.
+///
+/// The list emphasizes iteration order and speed over
+/// retrieval by name. When iterating over the list of
+/// callback sets, they are returned in the order they were
+/// added, not by name.
+class CSCallbackSetList {
+public:
+ /// @brief Constructor.
+ CSCallbackSetList() {}
+
+ /// @brief Adds a callback set to the list.
+ ///
+ /// @param name Name of the callback to add.
+ /// @param check_cb The check permissions callback to add.
+ /// @param entry_cb The CriticalSection entry callback to add.
+ /// @param exit_cb The CriticalSection exit callback to add.
+ ///
+ /// @throw BadValue if the name is already in the list,
+ /// the name is blank, or either callback is empty.
+ void addCallbackSet(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb);
+
+ /// @brief Removes a callback set from the list.
+ ///
+ /// @param name Name of the callback to remove.
+ /// If no such callback exists, it simply returns.
+ void removeCallbackSet(const std::string& name);
+
+ /// @brief Removes all callbacks from the list.
+ void removeAll();
+
+ /// @brief Fetches the list of callback sets.
+ const std::list<CSCallbackSet>& getCallbackSets();
+
+private:
+ /// @brief The list of callback sets.
+ std::list<CSCallbackSet> cb_sets_;
+};
+
+/// @brief Multi Threading Manager.
+///
+/// This singleton class holds the multi-threading mode.
+///
+/// See the @ref MultiThreadingLock class for a standard way of protecting code
+/// with a mutex. Or if you want to make it look like you're writing more code:
+/// @code
+/// if (MultiThreadingMgr::instance().getMode()) {
+/// multi-threaded code
+/// } else {
+/// single-threaded code
+/// }
+/// @endcode
+///
+/// For instance for a class protected by its mutex:
+/// @code
+/// namespace locked {
+/// void foo() { ... }
+/// } // end of locked namespace
+///
+/// void foo() {
+/// if (MultiThreadingMgr::instance().getMode()) {
+/// lock_guard<mutex> lock(mutex_);
+/// locked::foo();
+/// } else {
+/// locked::foo();
+/// }
+/// }
+/// @endcode
+class MultiThreadingMgr : public boost::noncopyable {
+public:
+ /// @brief Returns a single instance of Multi Threading Manager.
+ ///
+ /// MultiThreadingMgr is a singleton and this method is the only way
+ /// of accessing it.
+ ///
+ /// @return the single instance.
+ static MultiThreadingMgr& instance();
+
+ /// @brief Get the multi-threading mode.
+ ///
+ /// @return The current multi-threading mode: true if multi-threading is
+ /// enabled, false otherwise.
+ bool getMode() const;
+
+ /// @brief Set the multi-threading mode.
+ ///
+ /// @param enabled The new mode.
+ void setMode(bool enabled);
+
+ /// @brief Enter critical section.
+ ///
+ /// When entering @ref MultiThreadingCriticalSection, increment internal
+ /// counter so that any configuration change that might start the packet
+ /// thread pool is delayed until exiting the respective section.
+ /// If the internal counter is 0, then stop the thread pool.
+ ///
+ /// Invokes all CriticalSection entry callbacks. Has no effect in
+ /// single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all entry callbacks
+ /// without logging to avoid breaking the CS chain.
+ void enterCriticalSection();
+
+ /// @brief Exit critical section.
+ ///
+ /// When exiting @ref MultiThreadingCriticalSection, decrement internal
+ /// counter so that the dhcp thread pool can be started according to the
+ /// new configuration.
+ /// If the internal counter is 0, then start the thread pool.
+ ///
+ /// Invokes all CriticalSection exit callbacks. Has no effect in
+ /// single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all exit callbacks
+ /// without logging to avoid breaking the CS chain.
+ void exitCriticalSection();
+
+ /// @brief Is in critical section flag.
+ ///
+ /// @return The critical section flag.
+ bool isInCriticalSection();
+
+ /// @brief Get the dhcp thread pool.
+ ///
+ /// @return The dhcp thread pool.
+ ThreadPool<std::function<void()>>& getThreadPool();
+
+ /// @brief Get the configured dhcp thread pool size.
+ ///
+ /// @return The dhcp thread pool size.
+ uint32_t getThreadPoolSize() const;
+
+ /// @brief Set the configured dhcp thread pool size.
+ ///
+ /// @param size The dhcp thread pool size.
+ void setThreadPoolSize(uint32_t size);
+
+ /// @brief Get the configured dhcp packet queue size.
+ ///
+ /// @return The dhcp packet queue size.
+ uint32_t getPacketQueueSize();
+
+ /// @brief Set the configured dhcp packet queue size.
+ ///
+ /// @param size The dhcp packet queue size.
+ void setPacketQueueSize(uint32_t size);
+
+ /// @brief The system current detected hardware concurrency thread count.
+ ///
+ /// This function will return 0 if the value can not be determined.
+ ///
+ /// @return The thread count.
+ static uint32_t detectThreadCount();
+
+ /// @brief Apply the multi-threading related settings.
+ ///
+ /// This function should usually be called after constructing a
+ /// @ref MultiThreadingCriticalSection so that all thread pool parameters
+ /// can be safely applied.
+ ///
+ /// @param enabled The enabled flag: true if multi-threading is enabled,
+ /// false otherwise.
+ /// @param thread_count The desired number of threads: non 0 if explicitly
+ /// configured, 0 if auto scaling is desired
+ /// @param queue_size The desired thread queue size: non 0 if explicitly
+ /// configured, 0 for unlimited size
+ void apply(bool enabled, uint32_t thread_count, uint32_t queue_size);
+
+ /// @brief Adds a set of callbacks to the list of CriticalSection callbacks.
+ ///
+ /// @note Callbacks must be exception-safe, handling any errors themselves.
+ ///
+ /// @param name Name of the set of callbacks. This value is used by the
+ /// callback owner to add and remove them. Duplicates are not allowed.
+ /// @param check_cb Callback to check current thread permissions to call
+ /// the CriticalSection entry and exit callbacks.
+ /// @param entry_cb Callback to invoke upon CriticalSection entry. Cannot be
+ /// empty.
+ /// @param exit_cb Callback to invoke upon CriticalSection exit. Cannot be
+ /// empty.
+ void addCriticalSectionCallbacks(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb);
+
+ /// @brief Removes the set of callbacks associated with a given name
+ /// from the list of CriticalSection callbacks.
+ ///
+ /// If the name is not found in the list, it simply returns, otherwise
+ /// both callbacks registered under the name are removed.
+ ///
+ /// @param name Name of the set of callbacks to remove.
+ void removeCriticalSectionCallbacks(const std::string& name);
+
+ /// @brief Removes all callbacks in the list of CriticalSection callbacks.
+ void removeAllCriticalSectionCallbacks();
+
+protected:
+
+ /// @brief Constructor.
+ MultiThreadingMgr();
+
+ /// @brief Destructor.
+ virtual ~MultiThreadingMgr();
+
+private:
+
+ /// @brief Class method tests if current thread is allowed to enter the
+ /// @ref MultiThreadingCriticalSection and to invoke the entry and exit
+ /// callbacks.
+ ///
+ /// Has no effect in single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all check permission
+ /// callbacks without logging to avoid breaking the CS chain, except for the
+ /// @ref MultiThreadingInvalidOperation which needs to be propagated to the
+ /// scope of the @ref MultiThreadingCriticalSection constructor.
+ /// @throw MultiThreadingInvalidOperation if current thread has no
+ /// permission to enter CriticalSection.
+ void checkCallbacksPermissions();
+
+ /// @brief Class method which invokes CriticalSection entry callbacks.
+ ///
+ /// Has no effect in single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all entry callbacks
+ /// without logging to avoid breaking the CS chain.
+ void callEntryCallbacks();
+
+ /// @brief Class method which invokes CriticalSection exit callbacks.
+ ///
+ /// Has no effect in single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all exit callbacks
+ /// without logging to avoid breaking the CS chain.
+ void callExitCallbacks();
+
+ /// @brief The current multi-threading mode.
+ ///
+ /// The multi-threading flag: true if multi-threading is enabled, false
+ /// otherwise.
+ bool enabled_;
+
+ /// @brief The critical section count.
+ ///
+ /// In case the configuration is applied within a
+ /// @ref MultiThreadingCriticalSection, the thread pool should not be
+ /// started until leaving the respective section.
+ /// This handles multiple interleaved sections.
+ uint32_t critical_section_count_;
+
+ /// @brief The configured size of the dhcp thread pool.
+ uint32_t thread_pool_size_;
+
+ /// @brief Packet processing thread pool.
+ ThreadPool<std::function<void()>> thread_pool_;
+
+ /// @brief List of CriticalSection entry and exit callback sets.
+ CSCallbackSetList cs_callbacks_;
+};
+
+/// @brief RAII lock object to protect the code in the same scope with a mutex
+struct MultiThreadingLock {
+ /// @brief Constructor locks the mutex if multi-threading is enabled.
+ ///
+ /// The lock is automatically unlocked in the default destructor.
+ ///
+ /// @param mutex the mutex to be locked
+ MultiThreadingLock(std::mutex& mutex);
+
+private:
+ /// @brief object keeping the mutex locked for its entire lifetime
+ std::unique_lock<std::mutex> lock_;
+};
+
+/// @note: everything here MUST be used ONLY from the main thread.
+/// When called from a thread of the pool it can deadlock.
+
+/// @brief RAII class creating a critical section.
+///
+/// @note: the multi-threading mode MUST NOT be changed in the RAII
+/// @c MultiThreadingCriticalSection body.
+/// @note: starting and stopping the dhcp thread pool should be handled
+/// in the main thread, if done on one of the processing threads will cause a
+/// deadlock.
+/// This is mainly useful in hook commands which handle configuration
+/// changes.
+class MultiThreadingCriticalSection : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Entering the critical section. The dhcp thread pool instance will be
+ /// stopped so that all configuration changes can be safely applied.
+ MultiThreadingCriticalSection();
+
+ /// @brief Destructor.
+ ///
+ /// Leaving the critical section. The dhcp thread pool instance will be
+ /// started according to the new configuration.
+ virtual ~MultiThreadingCriticalSection();
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // MULTI_THREADING_MGR_H
diff --git a/src/lib/util/optional.h b/src/lib/util/optional.h
new file mode 100644
index 0000000..16d5dc0
--- /dev/null
+++ b/src/lib/util/optional.h
@@ -0,0 +1,201 @@
+// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef OPTIONAL_H
+#define OPTIONAL_H
+
+#include <exceptions/exceptions.h>
+#include <ostream>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief A template representing an optional value.
+///
+/// This template class encapsulates an optional value. The default implementation
+/// encapsulates numeric values, but additional specializations are defined
+/// as necessary to support other types od data.
+///
+/// This class includes a boolean flag which indicates if the encapsulated
+/// value is specified or unspecified. For example, a configuration parser
+/// for the DHCP server may use this class to represent a value of the
+/// configuration parameter which may appear in the configuration file, but
+/// is not mandatory. The value of the @c Optional may be initialized to
+/// "unspecified" initially. When the configuration parser finds that the
+/// particular parameter exists in the configuration file, the default value
+/// can be overridden and the value may be marked as "specified". If the
+/// parameter is not found, the value remains "unspecified" and the appropriate
+/// actions may be taken, e.g. the default value may be used.
+///
+/// @tparam Type of the encapsulated value.
+template<typename T>
+class Optional {
+public:
+
+ /// @brief Type of the encapsulated value.
+ typedef T ValueType;
+
+ /// @brief Assigns a new value value and marks it "specified".
+ ///
+ /// @tparam A Type of the value to be assigned. Typically this is @c T, but
+ /// may also be a type that can be cast to @c T.
+ /// @param other new actual value.
+ template<typename A>
+ Optional<T>& operator=(A other) {
+ default_ = other;
+ unspecified_ = false;
+ return (*this);
+ }
+
+ /// @brief Type cast operator.
+ ///
+ /// This operator converts the optional value to the actual value being
+ /// encapsulated.
+ ///
+ /// @return Encapsulated value.
+ operator T() const {
+ return (default_);
+ }
+
+ /// @brief Equality operator.
+ ///
+ /// @param other value to be compared.
+ bool operator==(const T& other) const {
+ return (default_ == other);
+ }
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other value to be compared.
+ bool operator!=(const T& other) const {
+ return (default_ != other);
+ }
+
+ /// @brief Default constructor.
+ ///
+ /// Sets the encapsulated value to 0 and marks it as "unspecified".
+ ///
+ /// The caller must ensure that the constructor of the class @c T
+ /// creates a valid object when invoked with 0 as an argument.
+ /// For example, a @c std::string(0) compiles but will crash at
+ /// runtime as 0 is not a valid pointer for the
+ /// @c std::string(const char*) constructor. Therefore, the
+ /// specialization of the @c Optional template for @c std::string
+ /// is provided below. It uses @c std::string default constructor.
+ ///
+ /// For any other type used with this template which doesn't come
+ /// with an appropriate constructor, the caller must create a
+ /// template specialization similar to the one provided for
+ /// @c std::string below.
+ Optional()
+ : default_(T(0)), unspecified_(true) {
+ }
+
+ /// @brief Constructor
+ ///
+ /// Sets an explicit value and marks it as "specified".
+ ///
+ /// @tparam A Type of the value to be assigned. Typically this is @c T, but
+ /// may also be a type that can be cast to @c T.
+ /// @param value value to be assigned.
+ /// @param unspecified initial state. Default is "unspecified".
+ template<typename A>
+ Optional(A value, const bool unspecified = false)
+ : default_(value), unspecified_(unspecified) {
+ }
+
+ /// @brief Retrieves the encapsulated value.
+ ///
+ /// @return the encapsulated value
+ T get() const {
+ return (default_);
+ }
+
+ /// @brief Retrieves the encapsulated value if specified, or the given value
+ /// otherwise.
+ ///
+ /// @param or_value the value it defaults to, if unspecified
+ ///
+ /// @return the encapsulated value or the default value
+ T valueOr(T const& or_value) const {
+ if (unspecified_) {
+ return or_value;
+ }
+ return default_;
+ }
+
+ /// @brief Modifies the flag that indicates whether the value is specified
+ /// or unspecified.
+ ///
+ /// @param unspecified new value of the flag. If it is @c true, the
+ /// value is marked as unspecified, otherwise it is marked as specified.
+ void unspecified(bool unspecified) {
+ unspecified_ = unspecified;
+ }
+
+ /// @brief Checks if the value has been specified or unspecified.
+ ///
+ /// @return true if the value hasn't been specified, false otherwise.
+ bool unspecified() const {
+ return (unspecified_);
+ }
+
+ /// @brief Checks if the encapsulated value is empty.
+ ///
+ /// This method can be overloaded in the template specializations that
+ /// are dedicated to strings, vectors etc.
+ ///
+ /// @throw isc::InvalidOperation.
+ bool empty() const {
+ isc_throw(isc::InvalidOperation, "call to empty() not supported");
+ }
+
+protected:
+ T default_; ///< Encapsulated value.
+ bool unspecified_; ///< Flag which indicates if the value is specified.
+};
+
+/// @brief Specialization of the default @c Optional constructor for
+/// strings.
+///
+/// It calls default string object constructor.
+template<>
+inline Optional<std::string>::Optional()
+ : default_(), unspecified_(true) {
+}
+
+/// @brief Specialization of the @c Optional::empty method for strings.
+///
+/// @return true if the value is empty, false otherwise.
+template<>
+inline bool Optional<std::string>::empty() const {
+ return (default_.empty());
+}
+
+/// @brief Inserts an optional value to a stream.
+///
+/// This function overloads the global operator<< to behave as in
+/// @c ostream::operator<< but applied to @c Optional objects.
+///
+/// @param os A @c std::ostream object to which the value is inserted.
+/// @param optional_value An @c Optional object to be inserted into
+/// a stream.
+/// @tparam Type of the value encapsulated by the @c Optional object.
+///
+/// @return A reference to the stream after insertion.
+template<typename T>
+std::ostream&
+operator<<(std::ostream& os, const Optional<T>& optional_value) {
+ os << optional_value.get();
+ return (os);
+}
+
+
+} // end of namespace isc::util
+} // end of namespace isc
+
+#endif // OPTIONAL_VALUE_H
diff --git a/src/lib/util/pid_file.cc b/src/lib/util/pid_file.cc
new file mode 100644
index 0000000..ef519b3
--- /dev/null
+++ b/src/lib/util/pid_file.cc
@@ -0,0 +1,93 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/pid_file.h>
+#include <cstdio>
+#include <signal.h>
+#include <unistd.h>
+#include <cerrno>
+
+namespace isc {
+namespace util {
+
+PIDFile::PIDFile(const std::string& filename)
+ : filename_(filename) {
+}
+
+PIDFile::~PIDFile() {
+}
+
+int
+PIDFile::check() const {
+ std::ifstream fs(filename_.c_str());
+ int pid;
+ bool good;
+
+ // If we weren't able to open the file treat
+ // it as if the process wasn't running
+ if (!fs.is_open()) {
+ return (false);
+ }
+
+ // Try to get the pid, get the status and get rid of the file
+ fs >> pid;
+ good = fs.good();
+ fs.close();
+
+ // If we weren't able to read a pid send back an exception
+ if (!good) {
+ isc_throw(PIDCantReadPID, "Unable to read PID from file '"
+ << filename_ << "'");
+ }
+
+ // If the process is still running return its pid.
+ if (kill(pid, 0) == 0) {
+ return (pid);
+ }
+
+ // No process
+ return (0);
+}
+
+void
+PIDFile::write() const {
+ write(getpid());
+}
+
+void
+PIDFile::write(int pid) const {
+ std::ofstream fs(filename_.c_str(), std::ofstream::trunc);
+
+ if (!fs.is_open()) {
+ isc_throw(PIDFileError, "Unable to open PID file '"
+ << filename_ << "' for write");
+ }
+
+ // File is open, write the pid.
+ fs << pid << std::endl;
+
+ bool good = fs.good();
+ fs.close();
+
+ if (!good) {
+ isc_throw(PIDFileError, "Unable to write to PID file '"
+ << filename_ << "'");
+ }
+}
+
+void
+PIDFile::deleteFile() const {
+ if ((remove(filename_.c_str()) != 0) &&
+ (errno != ENOENT)) {
+ isc_throw(PIDFileError, "Unable to delete PID file '"
+ << filename_ << "'");
+ }
+}
+
+} // namespace isc::util
+} // namespace isc
diff --git a/src/lib/util/pid_file.h b/src/lib/util/pid_file.h
new file mode 100644
index 0000000..a30640d
--- /dev/null
+++ b/src/lib/util/pid_file.h
@@ -0,0 +1,97 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PID_FILE_H
+#define PID_FILE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <fstream>
+#include <ostream>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown when an error occurs during PID file processing.
+class PIDFileError : public Exception {
+public:
+ PIDFileError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when an error occurs trying to read a PID
+/// from an opened file.
+class PIDCantReadPID : public Exception {
+public:
+ PIDCantReadPID(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Class to help with processing PID files
+///
+/// This is a utility class to manipulate PID file. It provides
+/// functions for writing and deleting a file containing a PID as
+/// well as for extracting a PID from a file and checking if the
+/// process is still running.
+class PIDFile {
+public:
+ /// @brief Constructor
+ ///
+ /// @param filename PID filename.
+ PIDFile(const std::string& filename);
+
+ /// @brief Destructor
+ ~PIDFile();
+
+ /// @brief Read the PID in from the file and check it.
+ ///
+ /// Read the PID in from the file then use it to see if
+ /// a process with that PID exists. If the file doesn't
+ /// exist treat it as the process not being there.
+ /// If the file exists but can't be read or it doesn't have
+ /// the proper format treat it as the process existing.
+ ///
+ /// @return returns the PID if it is in, 0 otherwise.
+ ///
+ /// @throw throws PIDCantReadPID if it was able to open the file
+ /// but was unable to read the PID from it.
+ int check() const;
+
+ /// @brief Write the PID to the file.
+ ///
+ /// @param pid the pid to write to the file.
+ ///
+ /// @throw throws PIDFileError if it can't open or write to the PID file.
+ void write(int) const;
+
+ /// @brief Get PID of the current process and write it to the file.
+ ///
+ /// @throw throws PIDFileError if it can't open or write to the PID file.
+ void write() const;
+
+ /// @brief Delete the PID file.
+ ///
+ /// @throw throws PIDFileError if it can't delete the PID file
+ void deleteFile() const;
+
+ /// @brief Returns the path to the PID file.
+ std::string getFilename() const {
+ return (filename_);
+ }
+
+private:
+ /// @brief PID filename
+ std::string filename_;
+};
+
+/// @brief Defines a shared pointer to a PIDFile
+typedef boost::shared_ptr<PIDFile> PIDFilePtr;
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // PID_FILE_H
diff --git a/src/lib/util/pointer_util.h b/src/lib/util/pointer_util.h
new file mode 100644
index 0000000..a775584
--- /dev/null
+++ b/src/lib/util/pointer_util.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef POINTER_UTIL_H
+#define POINTER_UTIL_H
+
+namespace isc {
+namespace util {
+
+/// @brief This function checks if two pointers are non-null and values
+/// are equal.
+///
+/// This function performs the typical comparison of values encapsulated by
+/// the smart pointers, with checking if the pointers are non-null.
+///
+/// @param ptr1 First pointer.
+/// @param ptr2 Second pointer.
+///
+/// @tparam T Pointer type, e.g. boost::shared_ptr, or boost::scoped_ptr.
+///
+/// @return true only if both pointers are non-null and the values which they
+/// point to are equal.
+template<typename T>
+inline bool equalValues(const T& ptr1, const T& ptr2) {
+ return (ptr1 && ptr2 && (*ptr1 == *ptr2));
+}
+
+/// @brief This function checks if two pointers are both null or both
+/// are non-null and they point to equal values.
+///
+/// @param ptr1 First pointer.
+/// @param ptr2 Second pointer.
+///
+/// @tparam T Pointer type, e.g. boost::shared_ptr, or boost::scoped_ptr.
+///
+/// @return true if both pointers are null or if they are both non-null
+/// and the values pointed to are equal.
+template<typename T>
+inline bool nullOrEqualValues(const T& ptr1, const T& ptr2) {
+ return ((!ptr1 && !ptr2) || equalValues(ptr1, ptr2));
+}
+
+} // end of namespace isc::util
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/util/python/Makefile.am b/src/lib/util/python/Makefile.am
new file mode 100644
index 0000000..460bf1a
--- /dev/null
+++ b/src/lib/util/python/Makefile.am
@@ -0,0 +1,3 @@
+noinst_SCRIPTS = const2hdr.py gen_wiredata.py
+EXTRA_DIST = const2hdr.py
+DISTCLEANFILES = gen_wiredata.py
diff --git a/src/lib/util/python/Makefile.in b/src/lib/util/python/Makefile.in
new file mode 100644
index 0000000..47a89e6
--- /dev/null
+++ b/src/lib/util/python/Makefile.in
@@ -0,0 +1,555 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/util/python
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = gen_wiredata.py
+CONFIG_CLEAN_VPATH_FILES =
+SCRIPTS = $(noinst_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/gen_wiredata.py.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_SCRIPTS = const2hdr.py gen_wiredata.py
+EXTRA_DIST = const2hdr.py
+DISTCLEANFILES = gen_wiredata.py
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/util/python/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/python/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+gen_wiredata.py: $(top_builddir)/config.status $(srcdir)/gen_wiredata.py.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(SCRIPTS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/util/python/const2hdr.py b/src/lib/util/python/const2hdr.py
new file mode 100644
index 0000000..e89a735
--- /dev/null
+++ b/src/lib/util/python/const2hdr.py
@@ -0,0 +1,56 @@
+# Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+'''
+The script takes a C++ file with constant definitions and creates a
+header file for the constants. It, however, does not understand C++
+syntax, it only does some heuristics to guess what looks like
+a constant and strips the values.
+
+The purpose is just to save some work with keeping both the source and
+header. The source syntax must be limited already, because it's used to
+generate the python module (by the
+lib/python/isc/util/pythonize_constants.py script).
+'''
+
+import sys
+import re
+
+if len(sys.argv) != 3:
+ sys.stderr.write("Usage: python3 ./const2hdr.py input.cc output.h\n")
+ sys.exit(1)
+
+[filename_in, filename_out] = sys.argv[1:3]
+
+preproc = re.compile('^#')
+constant = re.compile('^([a-zA-Z].*?[a-zA-Z_0-9]+)\\s*=.*;')
+
+with open(filename_in) as file_in, open(filename_out, "w") as file_out:
+ file_out.write("// This file is generated from " + filename_in + "\n" +
+ "// by the const2hdr.py script.\n" +
+ "// Do not edit, all changes will be lost.\n\n")
+ for line in file_in:
+ if preproc.match(line):
+ # There's only one preprocessor line in the .cc file. We abuse
+ # that to position the top part of the header.
+ file_out.write("#ifndef BIND10_COMMON_DEFS_H\n" +
+ "#define BIND10_COMMON_DEFS_H\n" +
+ "\n" +
+ "// \\file " + filename_out + "\n" +
+'''// \\brief Common shared constants\n
+// This file contains common definitions of constants used across the sources.
+// It includes, but is not limited to the definitions of messages sent from
+// one process to another. Since the names should be self-explanatory and
+// the variables here are used mostly to synchronize the same values across
+// multiple programs, separate documentation for each variable is not provided.
+''')
+ continue
+ # Extract the constant. Remove the values and add "extern"
+ line = constant.sub('extern \\1;', line)
+
+ file_out.write(line)
+
+ file_out.write("#endif\n")
diff --git a/src/lib/util/python/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
new file mode 100644
index 0000000..f1b51f3
--- /dev/null
+++ b/src/lib/util/python/gen_wiredata.py.in
@@ -0,0 +1,1448 @@
+#!@PYTHON@
+
+# Copyright (C) 2010-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Generator of various types of DNS data in the hex format.
+
+This script reads a human readable specification file (called "spec
+file" hereafter) that defines some type of DNS data (an RDATA, an RR,
+or a complete message) and dumps the defined data to a separate file
+as a "wire format" sequence parsable by the
+UnitTestUtil::readWireData() function (currently defined as part of
+libdns++ tests). Many DNS related tests involve wire format test
+data, so it will be convenient if we can define the data in a more
+intuitive way than writing the entire hex sequence by hand.
+
+Here is a simple example. Consider the following spec file:
+
+ [custom]
+ sections: a
+ [a]
+ as_rr: True
+
+When the script reads this file, it detects the file specifies a single
+component (called "section" here) that consists of a single A RDATA,
+which must be dumped as an RR (not only the part of RDATA). It then
+dumps the following content:
+
+ # A RR (QNAME=example.com Class=IN(1) TTL=86400 RDLEN=4)
+ 076578616d706c6503636f6d00 0001 0001 00015180 0004
+ # Address=192.0.2.1
+ c0000201
+
+As can be seen, the script automatically completes all variable
+parameters of RRs: owner name, class, TTL, RDATA length and data. For
+testing purposes many of these will be the same common one (like
+"example.com" or 192.0.2.1), so it would be convenient if we only have
+to specify non default parameters. To change the RDATA (i.e., the
+IPv4 address), we should add the following line at the end of the spec
+file:
+
+ address: 192.0.2.2
+
+Then the last two lines of the output file will be as follows:
+
+ # Address=192.0.2.2
+ c0000202
+
+In some cases we would rather specify malformed data for tests. This
+script has the ability to specify broken parameters for many types of
+data. For example, we can generate data that would look like an A RR
+but the RDLEN is 3 by adding the following line to the spec file:
+
+ rdlen: 3
+
+Then the first two lines of the output file will be as follows:
+
+ # A RR (QNAME=example.com Class=IN(1) TTL=86400 RDLEN=3)
+ 076578616d706c6503636f6d00 0001 0001 00015180 0003
+
+** USAGE **
+
+ gen_wiredata.py [-o output_file] spec_file
+
+If the -o option is missing, and if the spec_file has a suffix (such as
+in the form of "data.spec"), the output file name will be the prefix
+part of it (as in "data"); if -o is missing and the spec_file does not
+have a suffix, the script will fail.
+
+** SPEC FILE SYNTAX **
+
+A spec file accepted in this script should be in the form of a
+configuration file that is parsable by the Python's standard
+configparser module. In short, it consists of sections; each section
+is identified in the form of [section_name] followed by "name: value"
+entries. Lines beginning with # or ; will be treated as comments.
+Refer to the configparser module documentation for further details of
+the general syntax.
+
+This script has two major modes: the custom mode and the DNS query
+mode. The former generates an arbitrary combination of DNS message
+header, question section, RDATAs or RRs. It is mainly intended to
+generate a test data for a single type of RDATA or RR, or for
+complicated complete DNS messages. The DNS query mode is actually a
+special case of the custom mode, which is a shortcut to generate a
+simple DNS query message (with or without EDNS).
+
+* Custom mode syntax *
+
+By default this script assumes the DNS query mode. To specify the
+custom mode, there must be a special "custom" section in the spec
+file, which should contain 'sections' entry. This value of this
+entryis colon-separated string fields, each of which is either
+"header", "question", "edns", "name", or a string specifying an RR
+type. For RR types the string is lower-cased string mnemonic that
+identifies the type: 'a' for type A, 'ns' for type NS, and so on
+(note: in the current implementation it's case sensitive, and must be
+lower cased).
+
+Each of these fields is interpreted as a section name of the spec
+(configuration), and in that section parameters specific to the
+semantics of the field can be configured.
+
+A "header" section specifies the content of a DNS message header.
+See the documentation of the DNSHeader class of this module for
+configurable parameters.
+
+A "question" section specifies the content of a single question that
+is normally to be placed in the Question section of a DNS message.
+See the documentation of the DNSQuestion class of this module for
+configurable parameters.
+
+An "edns" section specifies the content of an EDNS OPT RR. See the
+documentation of the EDNS class of this module for configurable
+parameters.
+
+A "name" section specifies a domain name with or without compression.
+This is specifically intended to be used for testing name related
+functionalities and would rarely be used with other sections. See the
+documentation of the Name class of this module for configurable
+parameters.
+
+In a specific section for an RR or RDATA, possible entries depend on
+the type. But there are some common configurable entries. See the
+description of the RR class. The most important one would be "as_rr".
+It controls whether the entry should be treated as an RR (with name,
+type, class and TTL) or only as an RDATA. By default as_rr is
+"False", so if an entry is to be interpreted as an RR, an as_rr entry
+must be explicitly specified with a value of "True".
+
+Another common entry is "rdlen". It specifies the RDLEN field value
+of the RR (note: this is included when the entry is interpreted as
+RDATA, too). By default this value is automatically determined by the
+RR type and (it has a variable length) from other fields of RDATA, but
+as shown in the above example, it can be explicitly set, possibly to a
+bogus value for testing against invalid data.
+
+For type specific entries (and their defaults when provided), see the
+documentation of the corresponding Python class defined in this
+module. In general, there should be a class named the same mnemonic
+of the corresponding RR type for each supported type, and they are a
+subclass of the RR class. For example, the "NS" class is defined for
+RR type NS.
+
+Look again at the A RR example shown at the beginning of this
+description. There's a "custom" section, which consists of a
+"sections" entry whose value is a single "a", which means the data to
+be generated is an A RR or RDATA. There's a corresponding "a"
+section, which only specifies that it should be interpreted as an RR
+(all field values of the RR are derived from the default).
+
+If you want to generate a data sequence for two ore more RRs or
+RDATAs, you can specify them in the form of colon-separated fields for
+the "sections" entry. For example, to generate a sequence of A and NS
+RRs in that order, the "custom" section would be something like this:
+
+ [custom]
+ sections: a:ns
+
+and there must be an "ns" section in addition to "a".
+
+If a sequence of two or more RRs/RDATAs of the same RR type should be
+generated, these should be uniquely indexed with the "/" separator.
+For example, to generate two A RRs, the "custom" section would be as
+follows:
+
+ [custom]
+ sections: a/1:a/2
+
+and there must be "a/1" and "a/2" sections.
+
+Another practical example that would be used for many tests is to
+generate data for a complete DNS response message. The spec file of
+such an example configuration would look like as follows:
+
+ [custom]
+ sections: header:question:a
+ [header]
+ qr: 1
+ ancount: 1
+ [question]
+ [a]
+ as_rr: True
+
+With this configuration, this script will generate test data for a DNS
+response to a query for example.com/IN/A containing one corresponding
+A RR in the answer section.
+
+* DNS query mode syntax *
+
+If the spec file does not contain a "custom" section (that has a
+"sections" entry), this script assumes the DNS query mode. This mode
+is actually a special case of custom mode; it implicitly assumes the
+"sections" entry whose value is "header:question:edns".
+
+In this mode it is expected that the spec file also contains at least
+a "header" and "question" sections, and optionally an "edns" section.
+But the script does not warn or fail even if the expected sections are
+missing.
+
+* Entry value types *
+
+As described above, a section of the spec file accepts entries
+specific to the semantics of the section. They generally correspond
+to DNS message or RR fields.
+
+Many of them are expected to be integral values, for which either decimal or
+hexadecimal representation is accepted, for example:
+
+ rr_ttl: 3600
+ tag: 0x1234
+
+Some others are expected to be string. A string value does not have
+to be quoted:
+
+ address: 192.0.2.2
+
+but can also be quoted with single quotes:
+
+ address: '192.0.2.2'
+
+Note 1: a string that can be interpreted as an integer must be quoted.
+For example, if you want to set a "string" entry to "3600", it should
+be:
+
+ string: '3600'
+
+instead of
+
+ string: 3600
+
+Note 2: a string enclosed with double quotes is not accepted:
+
+ # This doesn't work:
+ address: "192.0.2.2"
+
+In general, string values are converted to hexadecimal sequences
+according to the semantics of the entry. For instance, a textual IPv4
+address in the above example will be converted to a hexadecimal
+sequence corresponding to a 4-byte integer. So, in many cases, the
+acceptable syntax for a particular string entry value should be
+obvious from the context. There are still some exceptional cases
+especially for complicated RR field values, for which the
+corresponding class documentation should be referenced.
+
+One special string syntax that would be worth noting is domain names,
+which would naturally be used in many kinds of entries. The simplest
+form of acceptable syntax is a textual representation of domain names
+such as "example.com" (note: names are always assumed to be
+"absolute", so the trailing dot can be omitted). But a domain name in
+the wire format can also contain a compression pointer. This script
+provides a simple support for name compression with a special notation
+of "ptr=nn" where nn is the numeric pointer value (decimal). For example,
+if the NSDNAME field of an NS RDATA is specified as follows:
+
+ nsname: ns.ptr=12
+
+this script will generate the following output:
+
+ # NS name=ns.ptr=12
+ 026e73c00c
+
+** EXTEND THE SCRIPT **
+
+This script is expected to be extended as we add more support for
+various types of RR. It is encouraged to add support for a new type
+of RR to this script as we see the need for testing that type. Here
+is a simple instruction of how to do that.
+
+Assume you are adding support for "FOO" RR. Also assume that the FOO
+RDATA contains a single field named "value".
+
+What you are expected to do is as follows:
+
+- Define a new class named "FOO" inherited from the RR class. Also
+ define a class variable named "value" for the FOO RDATA field (the
+ variable name can be different from the field name, but it's
+ convenient if it can be easily identifiable.) with an appropriate
+ default value (if possible):
+
+ class FOO(RR):
+ value = 10
+
+ The name of the variable will be (automatically) used as the
+ corresponding entry name in the spec file. So, a spec file that
+ sets this field to 20 would look like this:
+
+ [foo]
+ value: 20
+
+- Define the "dump()" method for class FOO. It must call
+ self.dump_header() (which is derived from class RR) at the
+ beginning. It then prints the RDATA field values in an appropriate
+ way. Assuming the value is a 16-bit integer field, a complete
+ dump() method would look like this:
+
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 2
+ self.dump_header(f, self.rdlen)
+ f.write('# Value=%d\\n' % (self.value))
+ f.write('%04x\\n' % (self.value))
+
+ The first f.write() call is not mandatory, but is encouraged to
+ be provided so that the generated files will be more human readable.
+ Depending on the complexity of the RDATA fields, the dump()
+ implementation would be more complicated. In particular, if the
+ RDATA length is variable and the RDLEN field value is not specified
+ in the spec file, the dump() method is normally expected to
+ calculate the correct length and pass it to dump_header(). See the
+ implementation of various derived classes of class RR for actual
+ examples.
+"""
+
+import configparser, re, time, socket, sys, base64
+from datetime import datetime
+from optparse import OptionParser
+
+re_hex = re.compile(r'^0x[0-9a-fA-F]+')
+re_decimal = re.compile(r'^\d+$')
+re_string = re.compile(r"\'(.*)\'$")
+
+dnssec_timefmt = '%Y%m%d%H%M%S'
+
+dict_qr = { 'query' : 0, 'response' : 1 }
+dict_opcode = { 'query' : 0, 'iquery' : 1, 'status' : 2, 'notify' : 4,
+ 'update' : 5 }
+rdict_opcode = dict([(dict_opcode[k], k.upper()) for k in dict_opcode.keys()])
+dict_rcode = { 'noerror' : 0, 'formerr' : 1, 'servfail' : 2, 'nxdomain' : 3,
+ 'notimp' : 4, 'refused' : 5, 'yxdomain' : 6, 'yxrrset' : 7,
+ 'nxrrset' : 8, 'notauth' : 9, 'notzone' : 10 }
+rdict_rcode = dict([(dict_rcode[k], k.upper()) for k in dict_rcode.keys()])
+dict_rrtype = { 'none' : 0, 'a' : 1, 'ns' : 2, 'md' : 3, 'mf' : 4, 'cname' : 5,
+ 'soa' : 6, 'mb' : 7, 'mg' : 8, 'mr' : 9, 'null' : 10,
+ 'wks' : 11, 'ptr' : 12, 'hinfo' : 13, 'minfo' : 14, 'mx' : 15,
+ 'txt' : 16, 'rp' : 17, 'afsdb' : 18, 'x25' : 19, 'isdn' : 20,
+ 'rt' : 21, 'nsap' : 22, 'nsap_tr' : 23, 'sig' : 24, 'key' : 25,
+ 'px' : 26, 'gpos' : 27, 'aaaa' : 28, 'loc' : 29, 'nxt' : 30,
+ 'srv' : 33, 'naptr' : 35, 'kx' : 36, 'cert' : 37, 'a6' : 38,
+ 'dname' : 39, 'opt' : 41, 'apl' : 42, 'ds' : 43, 'sshfp' : 44,
+ 'ipseckey' : 45, 'rrsig' : 46, 'nsec' : 47, 'dnskey' : 48,
+ 'dhcid' : 49, 'nsec3' : 50, 'nsec3param' : 51, 'tlsa' : 52, 'hip' : 55,
+ 'spf' : 99, 'unspec' : 103, 'tkey' : 249, 'tsig' : 250,
+ 'dlv' : 32769, 'ixfr' : 251, 'axfr' : 252, 'mailb' : 253,
+ 'maila' : 254, 'any' : 255, 'caa' : 257 }
+rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
+dict_rrclass = { 'in' : 1, 'ch' : 3, 'hs' : 4, 'any' : 255 }
+rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in \
+ dict_rrclass.keys()])
+dict_algorithm = { 'rsamd5' : 1, 'dh' : 2, 'dsa' : 3, 'ecc' : 4,
+ 'rsasha1' : 5 }
+dict_nsec3_algorithm = { 'reserved' : 0, 'sha1' : 1 }
+rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in \
+ dict_algorithm.keys()])
+rdict_nsec3_algorithm = dict([(dict_nsec3_algorithm[k], k.upper()) for k in \
+ dict_nsec3_algorithm.keys()])
+
+header_xtables = { 'qr' : dict_qr, 'opcode' : dict_opcode,
+ 'rcode' : dict_rcode }
+question_xtables = { 'rrtype' : dict_rrtype, 'rrclass' : dict_rrclass }
+
+def parse_value(value, xtable = {}):
+ if re.search(re_hex, value):
+ return int(value, 16)
+ if re.search(re_decimal, value):
+ return int(value)
+ m = re.match(re_string, value)
+ if m:
+ return m.group(1)
+ lovalue = value.lower()
+ if lovalue in xtable:
+ return xtable[lovalue]
+ return value
+
+def code_totext(code, dict):
+ if code in dict.keys():
+ return dict[code] + '(' + str(code) + ')'
+ return str(code)
+
+def encode_name(name, absolute=True):
+ # make sure the name is dot-terminated. duplicate dots will be ignored
+ # below.
+ name += '.'
+ labels = name.split('.')
+ wire = ''
+ for l in labels:
+ if len(l) > 4 and l[0:4] == 'ptr=':
+ # special meta-syntax for compression pointer
+ wire += '%04x' % (0xc000 | int(l[4:]))
+ break
+ if absolute or len(l) > 0:
+ wire += '%02x' % len(l)
+ wire += ''.join(['%02x' % ord(ch) for ch in l])
+ if len(l) == 0:
+ break
+ return wire
+
+def encode_string(name, len=None):
+ if type(name) is int and len is not None:
+ return '%0.*x' % (len * 2, name)
+ return ''.join(['%02x' % ord(ch) for ch in name])
+
+def encode_bytes(name, len=None):
+ if type(name) is int and len is not None:
+ return '%0.*x' % (len * 2, name)
+ return ''.join(['%02x' % ch for ch in name])
+
+def count_namelabels(name):
+ if name == '.': # special case
+ return 0
+ m = re.match('^(.*)\.$', name)
+ if m:
+ name = m.group(1)
+ return len(name.split('.'))
+
+def get_config(config, section, configobj, xtables = {}):
+ try:
+ for field in config.options(section):
+ value = config.get(section, field)
+ if field in xtables.keys():
+ xtable = xtables[field]
+ else:
+ xtable = {}
+ configobj.__dict__[field] = parse_value(value, xtable)
+ except configparser.NoSectionError:
+ return False
+ return True
+
+def print_header(f, input_file):
+ f.write('''###
+### This data file was auto-generated from ''' + input_file + '''
+###
+''')
+
+class Name:
+ '''Implements rendering a single domain name in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - name (string): A textual representation of the name, such as
+ 'example.com'.
+ - pointer (int): If specified, compression pointer will be
+ prepended to the generated data with the offset being the value
+ of this parameter.
+ '''
+
+ name = 'example.com'
+ pointer = None # no compression by default
+ def dump(self, f):
+ name = self.name
+ if self.pointer is not None:
+ if len(name) > 0 and name[-1] != '.':
+ name += '.'
+ name += 'ptr=%d' % self.pointer
+ name_wire = encode_name(name)
+ f.write('\n# DNS Name: %s' % self.name)
+ if self.pointer is not None:
+ f.write(' + compression pointer: %d' % self.pointer)
+ f.write('\n')
+ f.write('%s' % name_wire)
+ f.write('\n')
+
+class DNSHeader:
+ '''Implements rendering a DNS Header section in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - id (16-bit int):
+ - qr, aa, tc, rd, ra, ad, cd (0 or 1): Standard header bits as
+ defined in RFC1035 and RFC4035. If set to 1, the corresponding
+ bit will be set; if set to 0, it will be cleared.
+ - mbz (0-3): The reserved field of the 3rd and 4th octets of the
+ header.
+ - rcode (4-bit int or string): The RCODE field. If specified as a
+ string, it must be the commonly used textual mnemonic of the RCODEs
+ (NOERROR, FORMERR, etc, case insensitive).
+ - opcode (4-bit int or string): The OPCODE field. If specified as
+ a string, it must be the commonly used textual mnemonic of the
+ OPCODEs (QUERY, NOTIFY, etc, case insensitive).
+ - qdcount, ancount, nscount, arcount (16-bit int): The QD/AN/NS/AR
+ COUNT fields, respectively.
+ '''
+
+ id = 0x1035
+ (qr, aa, tc, rd, ra, ad, cd) = 0, 0, 0, 0, 0, 0, 0
+ mbz = 0
+ rcode = 0 # noerror
+ opcode = 0 # query
+ (qdcount, ancount, nscount, arcount) = 1, 0, 0, 0
+
+ def dump(self, f):
+ f.write('\n# Header Section\n')
+ f.write('# ID=' + str(self.id))
+ f.write(' QR=' + ('Response' if self.qr else 'Query'))
+ f.write(' Opcode=' + code_totext(self.opcode, rdict_opcode))
+ f.write(' Rcode=' + code_totext(self.rcode, rdict_rcode))
+ f.write('%s' % (' AA' if self.aa else ''))
+ f.write('%s' % (' TC' if self.tc else ''))
+ f.write('%s' % (' RD' if self.rd else ''))
+ f.write('%s' % (' AD' if self.ad else ''))
+ f.write('%s' % (' CD' if self.cd else ''))
+ f.write('\n')
+ f.write('%04x ' % self.id)
+ flag_and_code = 0
+ flag_and_code |= (self.qr << 15 | self.opcode << 14 | self.aa << 10 |
+ self.tc << 9 | self.rd << 8 | self.ra << 7 |
+ self.mbz << 6 | self.ad << 5 | self.cd << 4 |
+ self.rcode)
+ f.write('%04x\n' % flag_and_code)
+ f.write('# QDCNT=%d, ANCNT=%d, NSCNT=%d, ARCNT=%d\n' %
+ (self.qdcount, self.ancount, self.nscount, self.arcount))
+ f.write('%04x %04x %04x %04x\n' % (self.qdcount, self.ancount,
+ self.nscount, self.arcount))
+
+class DNSQuestion:
+ '''Implements rendering a DNS question in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - name (string): The QNAME. The string must be interpreted as a
+ valid domain name.
+ - rrtype (int or string): The question type. If specified
+ as an integer, it must be the 16-bit RR type value of the
+ covered type. If specified as a string, it must be the textual
+ mnemonic of the type.
+ - rrclass (int or string): The question class. If specified as an
+ integer, it must be the 16-bit RR class value of the covered
+ type. If specified as a string, it must be the textual mnemonic
+ of the class.
+ '''
+ name = 'example.com.'
+ rrtype = parse_value('A', dict_rrtype)
+ rrclass = parse_value('IN', dict_rrclass)
+
+ def dump(self, f):
+ f.write('\n# Question Section\n')
+ f.write('# QNAME=%s QTYPE=%s QCLASS=%s\n' %
+ (self.name,
+ code_totext(self.rrtype, rdict_rrtype),
+ code_totext(self.rrclass, rdict_rrclass)))
+ f.write(encode_name(self.name))
+ f.write(' %04x %04x\n' % (self.rrtype, self.rrclass))
+
+class EDNS:
+ '''Implements rendering EDNS OPT RR in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - name (string): The owner name of the OPT RR. The string must be
+ interpreted as a valid domain name.
+ - udpsize (16-bit int): The UDP payload size (set as the RR class)
+ - extrcode (8-bit int): The upper 8 bits of the extended RCODE.
+ - version (8-bit int): The EDNS version.
+ - do (int): The DNSSEC DO bit. The bit will be set if this value
+ is 1; otherwise the bit will be unset.
+ - mbz (15-bit int): The rest of the flags field.
+ - rdlen (16-bit int): The RDLEN field. Note: right now specifying
+ a non 0 value (except for making bogus data) doesn't make sense
+ because there is no way to configure RDATA.
+ '''
+ name = '.'
+ udpsize = 4096
+ extrcode = 0
+ version = 0
+ do = 0
+ mbz = 0
+ rdlen = 0
+ def dump(self, f):
+ f.write('\n# EDNS OPT RR\n')
+ f.write('# NAME=%s TYPE=%s UDPSize=%d ExtRcode=%s Version=%s DO=%d\n' %
+ (self.name, code_totext(dict_rrtype['opt'], rdict_rrtype),
+ self.udpsize, self.extrcode, self.version,
+ 1 if self.do else 0))
+
+ code_vers = (self.extrcode << 8) | (self.version & 0x00ff)
+ extflags = (self.do << 15) | (self.mbz & ~0x8000)
+ f.write('%s %04x %04x %04x %04x\n' %
+ (encode_name(self.name), dict_rrtype['opt'], self.udpsize,
+ code_vers, extflags))
+ f.write('# RDLEN=%d\n' % self.rdlen)
+ f.write('%04x\n' % self.rdlen)
+
+class RR:
+ '''This is a base class for various types of RR test data.
+ For each RR type (A, AAAA, NS, etc), we define a derived class of RR
+ to dump type specific RDATA parameters. This class defines parameters
+ common to all types of RDATA, namely the owner name, RR class and TTL.
+ The dump() method of derived classes are expected to call dump_header(),
+ whose default implementation is provided in this class. This method
+ decides whether to dump the test data as an RR (with name, type, class)
+ or only as RDATA (with its length), and dumps the corresponding data
+ via the specified file object.
+
+ By convention we assume derived classes are named after the common
+ standard mnemonic of the corresponding RR types. For example, the
+ derived class for the RR type SOA should be named "SOA".
+
+ Configurable parameters are as follows:
+ - as_rr (bool): Whether or not the data is to be dumped as an RR.
+ False by default.
+ - rr_name (string): The owner name of the RR. The string must be
+ interpreted as a valid domain name (compression pointer can be
+ contained). Default is 'example.com.'
+ - rr_class (string): The RR class of the data. Only meaningful
+ when the data is dumped as an RR. Default is 'IN'.
+ - rr_ttl (int): The TTL value of the RR. Only meaningful when
+ the data is dumped as an RR. Default is 86400 (1 day).
+ - rdlen (int): 16-bit RDATA length. It can be None (i.e. omitted
+ in the spec file), in which case the actual length of the
+ generated RDATA is automatically determined and used; if
+ negative, the RDLEN field will be omitted from the output data.
+ (Note that omitting RDLEN with as_rr being True is mostly
+ meaningless, although the script doesn't complain about it).
+ Default is None.
+ '''
+
+ def __init__(self):
+ self.as_rr = False
+ # only when as_rr is True, same for class/TTL:
+ self.rr_name = 'example.com'
+ self.rr_class = 'IN'
+ self.rr_ttl = 86400
+ self.rdlen = None
+
+ def dump_header(self, f, rdlen):
+ type_txt = self.__class__.__name__
+ type_code = parse_value(type_txt, dict_rrtype)
+ rdlen_spec = ''
+ rdlen_data = ''
+ if rdlen >= 0:
+ rdlen_spec = ', RDLEN=%d' % rdlen
+ rdlen_data = '%04x' % rdlen
+ if self.as_rr:
+ rrclass = parse_value(self.rr_class, dict_rrclass)
+ f.write('\n# %s RR (QNAME=%s Class=%s TTL=%d%s)\n' %
+ (type_txt, self.rr_name,
+ code_totext(rrclass, rdict_rrclass), self.rr_ttl,
+ rdlen_spec))
+ f.write('%s %04x %04x %08x %s\n' %
+ (encode_name(self.rr_name), type_code, rrclass,
+ self.rr_ttl, rdlen_data))
+ else:
+ f.write('\n# %s RDATA%s\n' % (type_txt, rdlen_spec))
+ f.write('%s\n' % rdlen_data)
+
+class A(RR):
+ '''Implements rendering A RDATA (of class IN) in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - address (string): The address field. This must be a valid textual
+ IPv4 address.
+ '''
+ RDLEN_DEFAULT = 4 # fixed by default
+ address = '192.0.2.1'
+
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = self.RDLEN_DEFAULT
+ self.dump_header(f, self.rdlen)
+ f.write('# Address=%s\n' % (self.address))
+ bin_address = socket.inet_aton(self.address)
+ f.write('%02x%02x%02x%02x\n' % (bin_address[0], bin_address[1],
+ bin_address[2], bin_address[3]))
+
+class AAAA(RR):
+ '''Implements rendering AAAA RDATA (of class IN) in the test data
+ format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - address (string): The address field. This must be a valid textual
+ IPv6 address.
+ '''
+ RDLEN_DEFAULT = 16 # fixed by default
+ address = '2001:db8::1'
+
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = self.RDLEN_DEFAULT
+ self.dump_header(f, self.rdlen)
+ f.write('# Address=%s\n' % (self.address))
+ bin_address = socket.inet_pton(socket.AF_INET6, self.address)
+ [f.write('%02x' % x) for x in bin_address]
+ f.write('\n')
+
+class NS(RR):
+ '''Implements rendering NS RDATA in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - nsname (string): The NSDNAME field. The string must be
+ interpreted as a valid domain name.
+ '''
+
+ nsname = 'ns.example.com'
+
+ def dump(self, f):
+ nsname_wire = encode_name(self.nsname)
+ if self.rdlen is None:
+ self.rdlen = len(nsname_wire) / 2
+ self.dump_header(f, self.rdlen)
+ f.write('# NS name=%s\n' % (self.nsname))
+ f.write('%s\n' % nsname_wire)
+
+class SOA(RR):
+ '''Implements rendering SOA RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - mname/rname (string): The MNAME/RNAME fields, respectively. The
+ string must be interpreted as a valid domain name.
+ - serial (32-bit int): The SERIAL field
+ - refresh (32-bit int): The REFRESH field
+ - retry (32-bit int): The RETRY field
+ - expire (32-bit int): The EXPIRE field
+ - minimum (32-bit int): The MINIMUM field
+ '''
+
+ mname = 'ns.example.com'
+ rname = 'root.example.com'
+ serial = 2010012601
+ refresh = 3600
+ retry = 300
+ expire = 3600000
+ minimum = 1200
+ def dump(self, f):
+ mname_wire = encode_name(self.mname)
+ rname_wire = encode_name(self.rname)
+ if self.rdlen is None:
+ self.rdlen = int(20 + len(mname_wire) / 2 + len(str(rname_wire)) / 2)
+ self.dump_header(f, self.rdlen)
+ f.write('# NNAME=%s RNAME=%s\n' % (self.mname, self.rname))
+ f.write('%s %s\n' % (mname_wire, rname_wire))
+ f.write('# SERIAL(%d) REFRESH(%d) RETRY(%d) EXPIRE(%d) MINIMUM(%d)\n' %
+ (self.serial, self.refresh, self.retry, self.expire,
+ self.minimum))
+ f.write('%08x %08x %08x %08x %08x\n' % (self.serial, self.refresh,
+ self.retry, self.expire,
+ self.minimum))
+
+class TXT(RR):
+ '''Implements rendering TXT RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - nstring (int): number of character-strings
+ - stringlenN (int) (int, N = 0, ..., nstring-1): the length of the
+ N-th character-string.
+ - stringN (string, N = 0, ..., nstring-1): the N-th
+ character-string.
+ - stringlen (int): the default string. If nstring >= 1 and the
+ corresponding stringlenN isn't specified in the spec file, this
+ value will be used. If this parameter isn't specified either,
+ the length of the string will be used. Note that it means
+ this parameter (or any stringlenN) doesn't have to be specified
+ unless you want to intentionally build a broken character string.
+ - string (string): the default string. If nstring >= 1 and the
+ corresponding stringN isn't specified in the spec file, this
+ string will be used.
+ '''
+
+ nstring = 1
+ stringlen = None
+ string = 'Test-String'
+
+ def dump(self, f):
+ stringlen_list = []
+ string_list = []
+ wirestring_list = []
+ for i in range(0, self.nstring):
+ key_string = 'string' + str(i)
+ if key_string in self.__dict__:
+ string_list.append(self.__dict__[key_string])
+ else:
+ string_list.append(self.string)
+ wirestring_list.append(encode_string(string_list[-1]))
+ key_stringlen = 'stringlen' + str(i)
+ if key_stringlen in self.__dict__:
+ stringlen_list.append(self.__dict__[key_stringlen])
+ else:
+ stringlen_list.append(self.stringlen)
+ if stringlen_list[-1] is None:
+ stringlen_list[-1] = int(len(wirestring_list[-1]) / 2)
+ if self.rdlen is None:
+ self.rdlen = int(len(''.join(wirestring_list)) / 2) + self.nstring
+ self.dump_header(f, self.rdlen)
+ for i in range(0, self.nstring):
+ f.write('# String Len=%d, String=\"%s\"\n' %
+ (stringlen_list[i], string_list[i]))
+ f.write('%02x%s%s\n' % (stringlen_list[i],
+ ' ' if len(wirestring_list[i]) > 0 else '',
+ wirestring_list[i]))
+
+class RP(RR):
+ '''Implements rendering RP RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - mailbox (string): The mailbox field.
+ - text (string): The text field.
+ These strings must be interpreted as a valid domain name.
+ '''
+ mailbox = 'root.example.com'
+ text = 'rp-text.example.com'
+ def dump(self, f):
+ mailbox_wire = encode_name(self.mailbox)
+ text_wire = encode_name(self.text)
+ if self.rdlen is None:
+ self.rdlen = (len(mailbox_wire) + len(text_wire)) / 2
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# MAILBOX=%s TEXT=%s\n' % (self.mailbox, self.text))
+ f.write('%s %s\n' % (mailbox_wire, text_wire))
+
+class SSHFP(RR):
+ '''Implements rendering SSHFP RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - algorithm (int): The algorithm number.
+ - fingerprint_type (int): The fingerprint type.
+ - fingerprint (string): The fingerprint.
+ '''
+ algorithm = 2
+ fingerprint_type = 1
+ fingerprint = '123456789abcdef67890123456789abcdef67890'
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 2 + (len(self.fingerprint) / 2)
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# ALGORITHM=%d FINGERPRINT_TYPE=%d FINGERPRINT=%s\n' % (self.algorithm,
+ self.fingerprint_type,
+ self.fingerprint))
+ f.write('%02x %02x %s\n' % (self.algorithm, self.fingerprint_type, self.fingerprint))
+
+class MINFO(RR):
+ '''Implements rendering MINFO RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - rmailbox (string): The rmailbox field.
+ - emailbox (string): The emailbox field.
+ These strings must be interpreted as a valid domain name.
+ '''
+ rmailbox = 'rmailbox.example.com'
+ emailbox = 'emailbox.example.com'
+ def dump(self, f):
+ rmailbox_wire = encode_name(self.rmailbox)
+ emailbox_wire = encode_name(self.emailbox)
+ if self.rdlen is None:
+ self.rdlen = (len(rmailbox_wire) + len(emailbox_wire)) / 2
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# RMAILBOX=%s EMAILBOX=%s\n' % (self.rmailbox, self.emailbox))
+ f.write('%s %s\n' % (rmailbox_wire, emailbox_wire))
+
+class AFSDB(RR):
+ '''Implements rendering AFSDB RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - subtype (16 bit int): The subtype field.
+ - server (string): The server field.
+ The string must be interpreted as a valid domain name.
+ '''
+ subtype = 1
+ server = 'afsdb.example.com'
+ def dump(self, f):
+ server_wire = encode_name(self.server)
+ if self.rdlen is None:
+ self.rdlen = 2 + len(server_wire) / 2
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# SUBTYPE=%d SERVER=%s\n' % (self.subtype, self.server))
+ f.write('%04x %s\n' % (self.subtype, server_wire))
+
+class CAA(RR):
+ '''Implements rendering CAA RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - flags (int): The flags field.
+ - tag (string): The tag field.
+ - value (string): The value field.
+ '''
+ flags = 0
+ tag = 'issue'
+ value = 'ca.example.net'
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 1 + 1 + len(self.tag) + len(self.value)
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# FLAGS=%d TAG=%s VALUE=%s\n' % \
+ (self.flags, self.tag, self.value))
+ f.write('%02x %02x ' % \
+ (self.flags, len(self.tag)))
+ f.write(encode_string(self.tag))
+ f.write(encode_string(self.value))
+ f.write('\n')
+
+class DNSKEY(RR):
+ '''Implements rendering DNSKEY RDATA in the test data format.
+
+ Configurable parameters are as follows (see code below for the
+ default values):
+ - flags (16-bit int): The flags field.
+ - protocol (8-bit int): The protocol field.
+ - algorithm (8-bit int): The algorithm field.
+ - digest (string): The key digest field.
+ '''
+ flags = 257
+ protocol = 3
+ algorithm = 5
+ digest = 'AAECAwQFBgcICQoLDA0ODw=='
+
+ def dump(self, f):
+ decoded_digest = base64.b64decode(bytes(self.digest, 'ascii'))
+ if self.rdlen is None:
+ self.rdlen = 4 + len(decoded_digest)
+ else:
+ self.rdlen = int(self.rdlen)
+
+ self.dump_header(f, self.rdlen)
+
+ f.write('# FLAGS=%d\n' % (self.flags))
+ f.write('%04x\n' % (self.flags))
+
+ f.write('# PROTOCOL=%d\n' % (self.protocol))
+ f.write('%02x\n' % (self.protocol))
+
+ f.write('# ALGORITHM=%d\n' % (self.algorithm))
+ f.write('%02x\n' % (self.algorithm))
+
+ f.write('# DIGEST=%s\n' % (self.digest))
+ f.write('%s\n' % (encode_bytes(decoded_digest)))
+
+class NSECBASE(RR):
+ '''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
+ these RRs. The NSEC and NSEC3 classes will be inherited from this
+ class.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - nbitmap (int): The number of type bitmaps.
+ The following three define the bitmaps. If suffixed with "N"
+ (0 <= N < nbitmaps), it means the definition for the N-th bitmap.
+ If there is no suffix (e.g., just "block", it means the default
+ for any unspecified values)
+ - block[N] (8-bit int): The Window Block.
+ - maplen[N] (8-bit int): The Bitmap Length. The default "maplen"
+ can also be unspecified (with being set to None), in which case
+ the corresponding length will be calculated from the bitmap.
+ - bitmap[N] (string): The Bitmap. This must be the hexadecimal
+ representation of the bitmap field. For example, for a bitmap
+ where the 7th and 15th bits (and only these bits) are set, it
+ must be '0101'. Note also that the value must be quoted with
+ single quotations because it could also be interpreted as an
+ integer.
+ '''
+ nbitmap = 1 # number of bitmaps
+ block = 0
+ maplen = None # default bitmap length, auto-calculate
+ bitmap = '040000000003' # an arbitrarily chosen bitmap sample
+ def dump(self, f):
+ # first, construct the bitmap data
+ block_list = []
+ maplen_list = []
+ bitmap_list = []
+ for i in range(0, self.nbitmap):
+ key_bitmap = 'bitmap' + str(i)
+ if key_bitmap in self.__dict__:
+ bitmap_list.append(self.__dict__[key_bitmap])
+ else:
+ bitmap_list.append(self.bitmap)
+ key_maplen = 'maplen' + str(i)
+ if key_maplen in self.__dict__:
+ maplen_list.append(self.__dict__[key_maplen])
+ else:
+ maplen_list.append(self.maplen)
+ if maplen_list[-1] is None: # calculate it if not specified
+ maplen_list[-1] = int(len(bitmap_list[-1]) / 2)
+ key_block = 'block' + str(i)
+ if key_block in self.__dict__:
+ block_list.append(self.__dict__[key_block])
+ else:
+ block_list.append(self.block)
+
+ # dump RR-type specific part (NSEC or NSEC3)
+ self.dump_fixedpart(f, 2 * self.nbitmap + \
+ int(len(''.join(bitmap_list)) / 2))
+
+ # dump the bitmap
+ for i in range(0, self.nbitmap):
+ f.write('# Bitmap: Block=%d, Length=%d\n' %
+ (block_list[i], maplen_list[i]))
+ f.write('%02x %02x %s\n' %
+ (block_list[i], maplen_list[i], bitmap_list[i]))
+
+class NSEC(NSECBASE):
+ '''Implements rendering NSEC RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - Type bitmap related parameters: see class NSECBASE
+ - nextname (string): The Next Domain Name field. The string must be
+ interpreted as a valid domain name.
+ '''
+
+ nextname = 'next.example.com'
+ def dump_fixedpart(self, f, bitmap_totallen):
+ name_wire = encode_name(self.nextname)
+ if self.rdlen is None:
+ # if rdlen needs to be calculated, it must be based on the bitmap
+ # length, because the configured maplen can be fake.
+ self.rdlen = int(len(name_wire) / 2) + bitmap_totallen
+ self.dump_header(f, self.rdlen)
+ f.write('# Next Name=%s (%d bytes)\n' % (self.nextname,
+ int(len(name_wire) / 2)))
+ f.write('%s\n' % name_wire)
+
+class NSEC3PARAM(RR):
+ '''Implements rendering NSEC3PARAM RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - hashalg (8-bit int): The Hash Algorithm field. Note that
+ currently the only defined algorithm is SHA-1, for which a value
+ of 1 will be used, and it's the default. So this implementation
+ does not support any string representation right now.
+ - optout (bool): The Opt-Out flag of the Flags field.
+ - mbz (7-bit int): The rest of the Flags field. This value will
+ be left shifted for 1 bit and then OR-ed with optout to
+ construct the complete Flags field.
+ - iterations (16-bit int): The Iterations field.
+ - saltlen (int): The Salt Length field.
+ - salt (string): The Salt field. It is converted to a sequence of
+ ascii codes and its hexadecimal representation will be used.
+ '''
+
+ hashalg = 1 # SHA-1
+ optout = False # opt-out flag
+ mbz = 0 # other flag fields (none defined yet)
+ iterations = 1
+ saltlen = 5
+ salt = 's' * saltlen
+
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 4 + 1 + len(self.salt)
+ self.dump_header(f, self.rdlen)
+ self._dump_params(f)
+
+ def _dump_params(self, f):
+ '''This method is intended to be shared with NSEC3 class.
+
+ '''
+
+ optout_val = 1 if self.optout else 0
+ f.write('# Hash Alg=%s, Opt-Out=%d, Other Flags=%0x, Iterations=%d\n' %
+ (code_totext(self.hashalg, rdict_nsec3_algorithm),
+ optout_val, self.mbz, self.iterations))
+ f.write('%02x %02x %04x\n' %
+ (self.hashalg, (self.mbz << 1) | optout_val, self.iterations))
+ f.write("# Salt Len=%d, Salt='%s'\n" % (self.saltlen, self.salt))
+ f.write('%02x%s%s\n' % (self.saltlen,
+ ' ' if len(self.salt) > 0 else '',
+ encode_string(self.salt)))
+
+class NSEC3(NSECBASE, NSEC3PARAM):
+ '''Implements rendering NSEC3 RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - Type bitmap related parameters: see class NSECBASE
+ - Hash parameter related parameters: see class NSEC3PARAM
+ - hashlen (int): The Hash Length field.
+ - hash (string): The Next Hashed Owner Name field. This parameter
+ is interpreted as "salt".
+ '''
+
+ hashlen = 20
+ hash = 'h' * hashlen
+ def dump_fixedpart(self, f, bitmap_totallen):
+ if self.rdlen is None:
+ # if rdlen needs to be calculated, it must be based on the bitmap
+ # length, because the configured maplen can be fake.
+ self.rdlen = 4 + 1 + len(self.salt) + 1 + len(self.hash) \
+ + bitmap_totallen
+ self.dump_header(f, self.rdlen)
+ self._dump_params(f)
+ f.write("# Hash Len=%d, Hash='%s'\n" % (self.hashlen, self.hash))
+ f.write('%02x%s%s\n' % (self.hashlen,
+ ' ' if len(self.hash) > 0 else '',
+ encode_string(self.hash)))
+
+class RRSIG(RR):
+ '''Implements rendering RRSIG RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - covered (int or string): The Type Covered field. If specified
+ as an integer, it must be the 16-bit RR type value of the
+ covered type. If specified as a string, it must be the textual
+ mnemonic of the type.
+ - algorithm (int or string): The Algorithm field. If specified
+ as an integer, it must be the 8-bit algorithm number as defined
+ in RFC4034. If specified as a string, it must be one of the keys
+ of dict_algorithm (case insensitive).
+ - labels (int): The Labels field. If omitted (the corresponding
+ variable being set to None), the number of labels of "signer"
+ (excluding the trailing null label as specified in RFC4034) will
+ be used.
+ - originalttl (32-bit int): The Original TTL field.
+ - expiration (32-bit int): The Expiration TTL field.
+ - inception (32-bit int): The Inception TTL field.
+ - tag (16-bit int): The Key Tag field.
+ - signer (string): The Signer's Name field. The string must be
+ interpreted as a valid domain name.
+ - signature (int): The Signature field. Right now only a simple
+ integer form is supported. A prefix of "0" will be prepended if
+ the resulting hexadecimal representation consists of an odd
+ number of characters.
+ '''
+
+ covered = 'A'
+ algorithm = 'RSASHA1'
+ labels = None # auto-calculate (#labels of signer)
+ originalttl = 3600
+ expiration = int(time.mktime(datetime.strptime('20100131120000',
+ dnssec_timefmt).timetuple()))
+ inception = int(time.mktime(datetime.strptime('20100101120000',
+ dnssec_timefmt).timetuple()))
+ tag = 0x1035
+ signer = 'example.com'
+ signature = 0x123456789abcdef123456789abcdef
+
+ def dump(self, f):
+ name_wire = encode_name(self.signer)
+ sig_wire = '%x' % self.signature
+ if len(sig_wire) % 2 != 0:
+ sig_wire = '0' + sig_wire
+ if self.rdlen is None:
+ self.rdlen = int(18 + len(name_wire) / 2 + len(str(sig_wire)) / 2)
+ self.dump_header(f, self.rdlen)
+
+ if type(self.covered) is str:
+ self.covered = dict_rrtype[self.covered.lower()]
+ if type(self.algorithm) is str:
+ self.algorithm = dict_algorithm[self.algorithm.lower()]
+ if self.labels is None:
+ self.labels = count_namelabels(self.signer)
+ f.write('# Covered=%s Algorithm=%s Labels=%d OrigTTL=%d\n' %
+ (code_totext(self.covered, rdict_rrtype),
+ code_totext(self.algorithm, rdict_algorithm), self.labels,
+ self.originalttl))
+ f.write('%04x %02x %02x %08x\n' % (self.covered, self.algorithm,
+ self.labels, self.originalttl))
+ f.write('# Expiration=%s, Inception=%s\n' %
+ (str(self.expiration), str(self.inception)))
+ f.write('%08x %08x\n' % (self.expiration, self.inception))
+ f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
+ f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
+
+class TKEY(RR):
+ '''Implements rendering TKEY RDATA in the test data format.
+
+ As a meta RR type TKEY uses some non common parameters. This
+ class overrides some of the default attributes of the RR class
+ accordingly:
+ - rr_class is set to 'ANY'
+ - rr_ttl is set to 0
+ Like other derived classes these can be overridden via the spec
+ file.
+
+ Other configurable parameters are as follows (see the description
+ of the same name of attribute for the default value):
+ - algorithm (string): The Algorithm Name field. The value is
+ generally interpreted as a domain name string, and will
+ typically be gss-tsig.
+ - inception (32-bit int): The Inception TTL field.
+ - expire (32-bit int): The Expire TTL field.
+ - mode (16-bit int): The Mode field.
+ - error (16-bit int): The Error field.
+ - key_len (int): The Key Len field.
+ - key (int or string): The Key field. If specified as an integer,
+ the integer value is used as the Key, possibly with prepended
+ 0's so that the total length will be key len. If specified as a
+ string, it is converted to a sequence of ascii codes and its
+ hexadecimal representation will be used. So, for example, if
+ "key" is set to 'abc', it will be converted to '616263'. Note
+ that in this case the length of "key" may not be equal to
+ key_len. If unspecified, the key_len number of '78' (ascii
+ code of 'x') will be used.
+ - other_len (int): The Other Len field.
+ - other_data (int or string): The Other Data field. This is
+ interpreted just like "key" except that other_len is used
+ instead of key_len. If unspecified this will be empty.
+ '''
+
+ algorithm = 'gss-tsig'
+ inception = int(time.mktime(datetime.strptime('20210501120000',
+ dnssec_timefmt).timetuple()))
+ expire = int(time.mktime(datetime.strptime('20210501130000',
+ dnssec_timefmt).timetuple()))
+ mode = 3 # GSS-API
+ error = 0
+ key_len = 32
+ key = None # use 'x' * key_len
+ other_len = 0
+ other_data = None
+
+ # TKEY has some special defaults
+ def __init__(self):
+ super().__init__()
+ self.rr_class = 'ANY'
+ self.rr_ttl = 0
+
+ def dump(self, f):
+ name_wire = encode_name(self.algorithm)
+ key_len = self.key_len
+ key = self.key
+ if key is None:
+ key = encode_string('x' * key_len)
+ else:
+ key = encode_string(self.key, key_len)
+ other_len = self.other_len
+ if other_len is None:
+ other_len = 0
+ other_data = self.other_data
+ if other_data is None:
+ other_data = ''
+ else:
+ other_data = encode_string(self.other_data, other_len)
+ if self.rdlen is None:
+ self.rdlen = int(len(name_wire) / 2 + 16 + len(key) / 2 + \
+ len(other_data) / 2)
+ self.dump_header(f, self.rdlen)
+ f.write('# Algorithm=%s\n' % self.algorithm)
+ f.write('%s\n' % name_wire)
+ f.write('# Inception=%d Expire=%d Mode=%d Error=%d\n' %
+ (self.inception, self.expire, self.mode, self.error))
+ f.write('%08x %08x %04x %04x\n' %
+ (self.inception, self.expire, self.mode, self.error))
+ f.write('# Key Len=%d Key=(see hex)\n' % key_len)
+ f.write('%04x%s\n' % (key_len, ' ' + key if len(key) > 0 else ''))
+ f.write('# Other-Len=%d Other-Data=(see hex)\n' % other_len)
+ f.write('%04x%s\n' % (other_len,
+ ' ' + other_data if len(other_data) > 0 else ''))
+
+class TLSA(RR):
+ '''Implements rendering TLSA RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - certificate_usage (int): The certificate usage field value.
+ - selector (int): The selector field value.
+ - matching_type (int): The matching type field value.
+ - certificate_association_data (string): The certificate association data.
+ '''
+ certificate_usage = 0
+ selector = 0
+ matching_type = 1
+ certificate_association_data = 'd2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971'
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 2 + (len(self.certificate_association_data) / 2)
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# CERTIFICATE_USAGE=%d SELECTOR=%d MATCHING_TYPE=%d CERTIFICATE_ASSOCIATION_DATA=%s\n' %\
+ (self.certificate_usage, self.selector, self.matching_type,\
+ self.certificate_association_data))
+ f.write('%02x %02x %02x %s\n' % (self.certificate_usage, self.selector, self.matching_type,\
+ self.certificate_association_data))
+
+class TSIG(RR):
+ '''Implements rendering TSIG RDATA in the test data format.
+
+ As a meta RR type TSIG uses some non common parameters. This
+ class overrides some of the default attributes of the RR class
+ accordingly:
+ - rr_class is set to 'ANY'
+ - rr_ttl is set to 0
+ Like other derived classes these can be overridden via the spec
+ file.
+
+ Other configurable parameters are as follows (see the description
+ of the same name of attribute for the default value):
+ - algorithm (string): The Algorithm Name field. The value is
+ generally interpreted as a domain name string, and will
+ typically be one of the standard algorithm names defined in
+ RFC4635. For convenience, however, a shortcut value "hmac-md5"
+ is allowed instead of the standard "hmac-md5.sig-alg.reg.int".
+ - time_signed (48-bit int): The Time Signed field.
+ - fudge (16-bit int): The Fudge field.
+ - mac_size (int): The MAC Size field. If omitted, the common value
+ determined by the algorithm will be used.
+ - mac (int or string): The MAC field. If specified as an integer,
+ the integer value is used as the MAC, possibly with prepended
+ 0's so that the total length will be mac_size. If specified as a
+ string, it is converted to a sequence of ascii codes and its
+ hexadecimal representation will be used. So, for example, if
+ "mac" is set to 'abc', it will be converted to '616263'. Note
+ that in this case the length of "mac" may not be equal to
+ mac_size. If unspecified, the mac_size number of '78' (ascii
+ code of 'x') will be used.
+ - original_id (16-bit int): The Original ID field.
+ - error (16-bit int): The Error field.
+ - other_len (int): The Other Len field.
+ - other_data (int or string): The Other Data field. This is
+ interpreted just like "mac" except that other_len is used
+ instead of mac_size. If unspecified this will be empty unless
+ the "error" is set to 18 (which means the "BADTIME" error), in
+ which case a hexadecimal representation of "time_signed + fudge
+ + 1" will be used.
+ '''
+
+ algorithm = 'hmac-sha256'
+ time_signed = 1286978795 # arbitrarily chosen default
+ fudge = 300
+ mac_size = None # use a common value for the algorithm
+ mac = None # use 'x' * mac_size
+ original_id = 2845 # arbitrarily chosen default
+ error = 0
+ other_len = None # 6 if error is BADTIME; otherwise 0
+ other_data = None # use time_signed + fudge + 1 for BADTIME
+ dict_macsize = { 'hmac-md5' : 16, 'hmac-sha1' : 20, 'hmac-sha256' : 32 }
+
+ # TSIG has some special defaults
+ def __init__(self):
+ super().__init__()
+ self.rr_class = 'ANY'
+ self.rr_ttl = 0
+
+ def dump(self, f):
+ if str(self.algorithm) == 'hmac-md5':
+ name_wire = encode_name('hmac-md5.sig-alg.reg.int')
+ else:
+ name_wire = encode_name(self.algorithm)
+ mac_size = self.mac_size
+ if mac_size is None:
+ if self.algorithm in self.dict_macsize.keys():
+ mac_size = self.dict_macsize[self.algorithm]
+ else:
+ raise RuntimeError('TSIG Mac Size cannot be determined')
+ mac = encode_string('x' * mac_size) if self.mac is None else \
+ encode_string(self.mac, mac_size)
+ other_len = self.other_len
+ if other_len is None:
+ # 18 = BADTIME
+ other_len = 6 if self.error == 18 else 0
+ other_data = self.other_data
+ if other_data is None:
+ other_data = '%012x' % (self.time_signed + self.fudge + 1) \
+ if self.error == 18 else ''
+ else:
+ other_data = encode_string(self.other_data, other_len)
+ if self.rdlen is None:
+ self.rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
+ len(other_data) / 2)
+ self.dump_header(f, self.rdlen)
+ f.write('# Algorithm=%s Time-Signed=%d Fudge=%d\n' %
+ (self.algorithm, self.time_signed, self.fudge))
+ f.write('%s %012x %04x\n' % (name_wire, self.time_signed, self.fudge))
+ f.write('# MAC Size=%d MAC=(see hex)\n' % mac_size)
+ f.write('%04x%s\n' % (mac_size, ' ' + mac if len(mac) > 0 else ''))
+ f.write('# Original-ID=%d Error=%d\n' % (self.original_id, self.error))
+ f.write('%04x %04x\n' % (self.original_id, self.error))
+ f.write('# Other-Len=%d Other-Data=(see hex)\n' % other_len)
+ f.write('%04x%s\n' % (other_len,
+ ' ' + other_data if len(other_data) > 0 else ''))
+
+# Build section-class mapping
+config_param = { 'name' : (Name, {}),
+ 'header' : (DNSHeader, header_xtables),
+ 'question' : (DNSQuestion, question_xtables),
+ 'edns' : (EDNS, {}) }
+for rrtype in dict_rrtype.keys():
+ # For any supported RR types add the tuple of (RR_CLASS, {}).
+ # We expect KeyError as not all the types are supported, and simply
+ # ignore them.
+ try:
+ cur_mod = sys.modules[__name__]
+ config_param[rrtype] = (cur_mod.__dict__[rrtype.upper()], {})
+ except KeyError:
+ pass
+
+def get_config_param(section):
+ s = section
+ m = re.match('^([^:]+)/\d+$', section)
+ if m:
+ s = m.group(1)
+ return config_param[s]
+
+usage = '''usage: %prog [options] input_file'''
+
+if __name__ == "__main__":
+ parser = OptionParser(usage=usage)
+ parser.add_option('-o', '--output', action='store', dest='output',
+ default=None, metavar='FILE',
+ help='output file name [default: prefix of input_file]')
+ (options, args) = parser.parse_args()
+
+ if len(args) == 0:
+ parser.error('input file is missing')
+ configfile = args[0]
+
+ outputfile = options.output
+ if not outputfile:
+ m = re.match('(.*)\.[^.]+$', configfile)
+ if m:
+ outputfile = m.group(1)
+ else:
+ raise ValueError('output file is not specified and input file is not in the form of "output_file.suffix"')
+
+ # DeprecationWarning: use ConfigParser directly
+ config = configparser.SafeConfigParser()
+ config.read(configfile)
+
+ output = open(outputfile, 'w')
+
+ print_header(output, configfile)
+
+ # First try the 'custom' mode; if it fails assume the query mode.
+ try:
+ sections = config.get('custom', 'sections').split(':')
+ except configparser.NoSectionError:
+ sections = ['header', 'question', 'edns']
+
+ for s in sections:
+ section_param = get_config_param(s)
+ (obj, xtables) = (section_param[0](), section_param[1])
+ if get_config(config, s, obj, xtables):
+ obj.dump(output)
+
+ output.close()
diff --git a/src/lib/util/range_utilities.h b/src/lib/util/range_utilities.h
new file mode 100644
index 0000000..a14b677
--- /dev/null
+++ b/src/lib/util/range_utilities.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2012-2019,2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RANGE_UTIL_H
+#define RANGE_UTIL_H 1
+
+#include <stdlib.h>
+#include <algorithm>
+#include <functional>
+
+// This header contains useful methods for conduction operations on
+// a range of container elements. Currently the collection is limited,
+// but it is expected to grow.
+
+namespace isc {
+namespace util {
+
+/// @brief Checks if specified range in a container contains only zeros
+///
+/// @param begin beginning of the range
+/// @param end end of the range
+///
+/// @return true if there are only zeroes, false otherwise
+template <typename Iterator>
+bool
+isRangeZero(Iterator begin, Iterator end) {
+ return (std::find_if(begin, end, [] (int x) { return (0 != x); })
+ == end);
+}
+
+/// @brief Fill in specified range with a random data.
+///
+/// Make sure that random number generator is initialized
+/// properly. Otherwise you will get the same pseudo-random sequence
+/// after every start of your process. Calling srand() is enough. This
+/// method uses default rand(), which is usually a LCG pseudo-random
+/// number generator, so it is not suitable for security
+/// purposes. Please use cryptolink RNG if you are doing anything
+/// related with security.
+///
+/// PRNG initialization is left out of this function on purpose. It may
+/// be initialized to specific value on purpose, e.g. to repeat exactly
+/// the same sequence in a test.
+///
+/// @param begin
+/// @param end
+template <typename Iterator>
+void
+fillRandom(Iterator begin, Iterator end) {
+ for (Iterator x = begin; x != end; ++x) {
+ *x = random();
+ }
+}
+
+} // end of isc::util namespace
+} // end of isc namespace
+
+#endif // RANGE_UTIL_H
diff --git a/src/lib/util/readwrite_mutex.h b/src/lib/util/readwrite_mutex.h
new file mode 100644
index 0000000..14d54e6
--- /dev/null
+++ b/src/lib/util/readwrite_mutex.h
@@ -0,0 +1,187 @@
+// Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef READWRITE_MUTEX_H
+#define READWRITE_MUTEX_H
+
+/// @file readwrite_mutex.h
+///
+/// Standard implementation of read-write mutexes with writer preference
+/// using C++11 mutex and condition variable.
+/// As we need only the RAII wrappers implement only used methods.
+
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <climits>
+#include <condition_variable>
+#include <mutex>
+
+namespace isc {
+namespace util {
+
+/// @brief Read-Write Mutex.
+///
+/// The code is based on Howard Hinnant's reference implementation
+/// for C++17 shared_mutex.
+class ReadWriteMutex : public boost::noncopyable {
+public:
+
+ /// Constants.
+
+ /// @brief The write entered flag (higher bit so 2^31).
+ static const unsigned WRITE_ENTERED =
+ 1U << (sizeof(unsigned) * CHAR_BIT - 1);
+
+ /// @brief The maximum number of readers (flag complement so 2^31 - 1).
+ static const unsigned MAX_READERS = ~WRITE_ENTERED;
+
+ /// @brief Constructor.
+ ReadWriteMutex() : state_(0) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// @note: do not check that state is 0 as there is nothing very
+ /// useful to do in this case...
+ virtual ~ReadWriteMutex() {
+ std::lock_guard<std::mutex> lk(mutex_);
+ }
+
+ /// @brief Lock write.
+ void writeLock() {
+ std::unique_lock<std::mutex> lk(mutex_);
+ // Wait until the write entered flag can be set.
+ gate1_.wait(lk, [&]() { return (!writeEntered()); });
+ state_ |= WRITE_ENTERED;
+ // Wait until there are no more readers.
+ gate2_.wait(lk, [&]() { return (readers() == 0); });
+ }
+
+ /// @brief Unlock write.
+ ///
+ /// @note: do not check that WRITE_ENTERED was set.
+ void writeUnlock() {
+ std::lock_guard<std::mutex> lk(mutex_);
+ state_ = 0;
+ // Wake-up waiting threads when exiting the guard.
+ gate1_.notify_all();
+ }
+
+ /// @brief Lock read.
+ void readLock() {
+ std::unique_lock<std::mutex> lk(mutex_);
+ // Wait if there is a writer or if readers overflow.
+ gate1_.wait(lk, [&]() { return (state_ < MAX_READERS); });
+ ++state_;
+ }
+
+ /// @brief Unlock read.
+ ///
+ /// @note: do not check that there is a least one reader.
+ void readUnlock() {
+ std::lock_guard<std::mutex> lk(mutex_);
+ unsigned prev = state_--;
+ if (writeEntered()) {
+ if (readers() == 0) {
+ // Last reader: wake up a waiting writer.
+ gate2_.notify_one();
+ }
+ } else {
+ if (prev == MAX_READERS) {
+ // Reader overflow: wake up one waiting reader.
+ gate1_.notify_one();
+ }
+ }
+ }
+
+private:
+
+ /// Helpers.
+
+ /// @brief Check if the write entered flag is set.
+ bool writeEntered() const {
+ return (state_ & WRITE_ENTERED);
+ }
+
+ /// @brief Return the number of readers.
+ unsigned readers() const {
+ return (state_ & MAX_READERS);
+ }
+
+ /// Members.
+
+ /// @brief Mutex.
+ ///
+ /// Used to protect the state and in condition variables.
+ std::mutex mutex_;
+
+ /// @brief First condition variable.
+ ///
+ /// Used to block while the write entered flag is set or readers overflow.
+ std::condition_variable gate1_;
+
+ /// @brief Second condition variable.
+ ///
+ /// Used to block writers until the reader count decrements to zero.
+ std::condition_variable gate2_;
+
+ /// @brief State.
+ ///
+ /// Used to handle the write entered flag and the reader count.
+ unsigned state_;
+};
+
+/// @brief Read mutex RAII handler.
+///
+/// The constructor acquires the lock, the destructor releases it.
+class ReadLockGuard : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param rw_mutex The read mutex.
+ ReadLockGuard(ReadWriteMutex& rw_mutex) : rw_mutex_(rw_mutex) {
+ rw_mutex_.readLock();
+ }
+
+ /// @brief Destructor.
+ virtual ~ReadLockGuard() {
+ rw_mutex_.readUnlock();
+ }
+
+private:
+ /// @brief The read-write mutex.
+ ReadWriteMutex& rw_mutex_;
+
+};
+
+/// @brief Write mutex RAII handler.
+///
+/// The constructor acquires the lock, the destructor releases it.
+class WriteLockGuard : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param rw_mutex The write mutex.
+ WriteLockGuard(ReadWriteMutex& rw_mutex) : rw_mutex_(rw_mutex) {
+ rw_mutex_.writeLock();
+ }
+
+ /// @brief Destructor.
+ virtual ~WriteLockGuard() {
+ rw_mutex_.writeUnlock();
+ }
+
+private:
+ /// @brief The read-write mutex.
+ ReadWriteMutex& rw_mutex_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // READWRITE_MUTEX_H
diff --git a/src/lib/util/reconnect_ctl.cc b/src/lib/util/reconnect_ctl.cc
new file mode 100644
index 0000000..265ff2f
--- /dev/null
+++ b/src/lib/util/reconnect_ctl.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <util/reconnect_ctl.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace util {
+
+std::string
+ReconnectCtl::onFailActionToText(OnFailAction action) {
+ switch (action) {
+ case OnFailAction::STOP_RETRY_EXIT:
+ return ("stop-retry-exit");
+ case OnFailAction::SERVE_RETRY_EXIT:
+ return ("serve-retry-exit");
+ case OnFailAction::SERVE_RETRY_CONTINUE:
+ return ("serve-retry-continue");
+ }
+ return ("invalid-action-type");
+}
+
+OnFailAction
+ReconnectCtl::onFailActionFromText(const std::string& text) {
+ if (text == "stop-retry-exit") {
+ return (OnFailAction::STOP_RETRY_EXIT);
+ } else if (text == "serve-retry-exit") {
+ return (OnFailAction::SERVE_RETRY_EXIT);
+ } else if (text == "serve-retry-continue") {
+ return (OnFailAction::SERVE_RETRY_CONTINUE);
+ } else {
+ isc_throw(BadValue, "Invalid action on connection loss: " << text);
+ }
+}
+
+}
+}
diff --git a/src/lib/util/reconnect_ctl.h b/src/lib/util/reconnect_ctl.h
new file mode 100644
index 0000000..b9f4a32
--- /dev/null
+++ b/src/lib/util/reconnect_ctl.h
@@ -0,0 +1,143 @@
+// Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RECONNECT_CTL_H
+#define RECONNECT_CTL_H
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief Type of action to take on connection loss.
+enum class OnFailAction {
+ STOP_RETRY_EXIT,
+ SERVE_RETRY_EXIT,
+ SERVE_RETRY_CONTINUE
+};
+
+/// @brief Warehouses reconnect control values
+///
+/// When any connection loses connectivity to its backend, it
+/// creates an instance of this class based on its configuration parameters and
+/// passes the instance into connection's lost callback. This allows
+/// the layer(s) above the connection to know how to proceed.
+///
+class ReconnectCtl {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param backend_type type of the caller backend.
+ /// @param timer_name timer associated to this object.
+ /// @param max_retries maximum number of reconnect attempts to make.
+ /// @param retry_interval amount of time to between reconnect attempts.
+ /// @param action which should be taken on connection loss.
+ ReconnectCtl(const std::string& backend_type, const std::string& timer_name,
+ unsigned int max_retries, unsigned int retry_interval,
+ OnFailAction action) :
+ backend_type_(backend_type), timer_name_(timer_name),
+ max_retries_(max_retries), retries_left_(max_retries),
+ retry_interval_(retry_interval), action_(action) {}
+
+ /// @brief Returns the type of the caller backend.
+ std::string backendType() const {
+ return (backend_type_);
+ }
+
+ /// @brief Returns the associated timer name.
+ ///
+ /// @return the associated timer.
+ std::string timerName() const {
+ return (timer_name_);
+ }
+
+ /// @brief Decrements the number of retries remaining
+ ///
+ /// Each call decrements the number of retries by one until zero is reached.
+ /// @return true the number of retries remaining is greater than zero.
+ bool checkRetries() {
+ return (retries_left_ ? --retries_left_ : false);
+ }
+
+ /// @brief Returns the maximum number of retries allowed.
+ unsigned int maxRetries() const {
+ return (max_retries_);
+ }
+
+ /// @brief Returns the number for retries remaining.
+ unsigned int retriesLeft() const {
+ return (retries_left_);
+ }
+
+ /// @brief Returns an index of current retry.
+ unsigned int retryIndex() const {
+ return (max_retries_ - retries_left_);
+ }
+
+ /// @brief Returns the amount of time to wait between reconnect attempts.
+ unsigned int retryInterval() const {
+ return (retry_interval_);
+ }
+
+ /// @brief Resets the retries count.
+ void resetRetries() {
+ retries_left_ = max_retries_;
+ }
+
+ /// @brief Return true if the connection loss should affect the service,
+ /// false otherwise
+ bool alterServiceState() const {
+ return (action_ == OnFailAction::STOP_RETRY_EXIT);
+ }
+
+ /// @brief Return true if the connection recovery mechanism should shut down
+ /// the server on failure, false otherwise.
+ bool exitOnFailure() const {
+ return ((action_ == OnFailAction::STOP_RETRY_EXIT) ||
+ (action_ == OnFailAction::SERVE_RETRY_EXIT));
+ }
+
+ /// @brief Convert action to string.
+ ///
+ /// @param action The action type to be converted to text.
+ /// @return The text representation of the action type.
+ static std::string onFailActionToText(OnFailAction action);
+
+ /// @brief Convert string to action.
+ ///
+ /// @param text The text to be converted to action type.
+ /// @return The action type corresponding to the text representation.
+ static OnFailAction onFailActionFromText(const std::string& text);
+
+private:
+
+ /// @brief Caller backend type.
+ const std::string backend_type_;
+
+ /// @brief Timer associated to this object.
+ std::string timer_name_;
+
+ /// @brief Maximum number of retry attempts to make.
+ unsigned int max_retries_;
+
+ /// @brief Number of attempts remaining.
+ unsigned int retries_left_;
+
+ /// @brief The amount of time to wait between reconnect attempts.
+ unsigned int retry_interval_;
+
+ /// @brief Action to take on connection loss.
+ OnFailAction action_;
+};
+
+/// @brief Pointer to an instance of ReconnectCtl
+typedef boost::shared_ptr<ReconnectCtl> ReconnectCtlPtr;
+
+}
+}
+
+#endif // RECONNECT_CTL_H
diff --git a/src/lib/util/staged_value.h b/src/lib/util/staged_value.h
new file mode 100644
index 0000000..3f85f0e
--- /dev/null
+++ b/src/lib/util/staged_value.h
@@ -0,0 +1,118 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef STAGED_VALUE_H
+#define STAGED_VALUE_H
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief This class implements set/commit mechanism for a single object.
+///
+/// In some cases it is desired to set value for an object while keeping
+/// ability to revert to an original value under certain conditions.
+/// This is often desired for objects holding some part of application's
+/// configuration. Configuration is usually a multi-step process and
+/// may fail on almost any stage. If this happens, the last good
+/// configuration should be used. This implies that some of the state
+/// of some of the objects needs to be reverted.
+///
+/// This class implements a mechanism for setting and committing a value.
+/// Until the new value has been committed it is possible to revert to
+/// an original value.
+///
+/// @tparam ValueType Type of the value represented by this class.
+template<typename ValueType>
+class StagedValue : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes the default value.
+ StagedValue()
+ : staging_(new ValueType()), current_(new ValueType()),
+ modified_(false) {
+ }
+
+ /// @brief Retrieves current value.
+ ///
+ /// If the value hasn't been modified since last commit, reset or
+ /// revert operation, a committed value is returned. If the value
+ /// has been modified, the modified value is returned.
+ const ValueType& getValue() const {
+ return (modified_ ? *staging_ : *current_);
+ }
+
+ /// @brief Sets new value.
+ ///
+ /// @param new_value New value to be assigned.
+ void setValue(const ValueType& new_value) {
+ *staging_ = new_value;
+ modified_ = true;
+ }
+
+ /// @brief Commits a value.
+ void commit() {
+ // Only apply changes if any modifications made.
+ if (modified_) {
+ current_ = staging_;
+ }
+ revert();
+ }
+
+ /// @brief Resets value to defaults.
+ void reset() {
+ revert();
+ current_.reset(new ValueType());
+ }
+
+ /// @brief Reverts any modifications since last commit.
+ void revert() {
+ staging_.reset(new ValueType());
+ modified_ = false;
+ }
+
+ /// @brief Assignment operator.
+ ///
+ /// @param value New value to be assigned.
+ /// @return Reference to this.
+ StagedValue& operator=(const ValueType& value) {
+ setValue(value);
+ return (*this);
+ }
+
+ /// @brief Conversion operator to value type.
+ ///
+ /// @return Reference to value represented by this object.
+ operator const ValueType&() const {
+ return (getValue());
+ }
+
+private:
+
+ /// @brief Pointer to staging value.
+ ///
+ /// This value holds any modifications made.
+ boost::shared_ptr<ValueType> staging_;
+
+ /// @brief Pointer to committed value.
+ ///
+ /// This value holds last committed changes.
+ boost::shared_ptr<ValueType> current_;
+
+ /// @brief Boolean flag which indicates if any modifications have been
+ /// applied since last commit.
+ bool modified_;
+
+};
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // STAGED_VALUE_H
diff --git a/src/lib/util/state_model.cc b/src/lib/util/state_model.cc
new file mode 100644
index 0000000..6c9a13d
--- /dev/null
+++ b/src/lib/util/state_model.cc
@@ -0,0 +1,465 @@
+// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <util/state_model.h>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/********************************** State *******************************/
+
+State::State(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing)
+ : LabeledValue(value, label), handler_(handler), pausing_(state_pausing),
+ was_paused_(false) {
+}
+
+State::~State() {
+}
+
+void
+State::run() {
+ (handler_)();
+}
+
+bool
+State::shouldPause() {
+ if ((pausing_ == STATE_PAUSE_ALWAYS) ||
+ ((pausing_ == STATE_PAUSE_ONCE) && (!was_paused_))) {
+ was_paused_ = true;
+ return (true);
+ }
+ return (false);
+}
+
+/********************************** StateSet *******************************/
+
+StateSet::StateSet() {
+}
+
+StateSet::~StateSet() {
+}
+
+void
+StateSet::add(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing) {
+ try {
+ LabeledValueSet::add(LabeledValuePtr(new State(value, label, handler,
+ state_pausing)));
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "StateSet: cannot add state :" << ex.what());
+ }
+
+}
+
+const StatePtr
+StateSet::getState(int value) {
+ if (!isDefined(value)) {
+ isc_throw(StateModelError," StateSet: state is undefined");
+ }
+
+ // Since we have to use dynamic casting, to get a state pointer
+ // we can't return a reference.
+ StatePtr state = boost::dynamic_pointer_cast<State>(get(value));
+ return (state);
+}
+
+/********************************** StateModel *******************************/
+
+
+// Common state model states
+const int StateModel::NEW_ST;
+const int StateModel::END_ST;
+
+const int StateModel::SM_DERIVED_STATE_MIN;
+
+// Common state model events
+const int StateModel::NOP_EVT;
+const int StateModel::START_EVT;
+const int StateModel::END_EVT;
+const int StateModel::FAIL_EVT;
+
+const int StateModel::SM_DERIVED_EVENT_MIN;
+
+StateModel::StateModel() : events_(), states_(), dictionaries_initted_(false),
+ curr_state_(NEW_ST), prev_state_(NEW_ST),
+ last_event_(NOP_EVT), next_event_(NOP_EVT),
+ on_entry_flag_(false), on_exit_flag_(false),
+ paused_(false), mutex_(new std::mutex) {
+}
+
+StateModel::~StateModel(){
+}
+
+void
+StateModel::startModel(const int start_state) {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(start_state);
+
+ // Start running the model.
+ runModel(START_EVT);
+}
+
+void
+StateModel::runModel(unsigned int run_event) {
+ /// If the dictionaries aren't built bail out.
+ if (!dictionaries_initted_) {
+ abortModel("runModel invoked before model has been initialized");
+ }
+
+ try {
+ // Seed the loop with the given event as the next to process.
+ postNextEvent(run_event);
+ do {
+ // Invoke the current state's handler. It should consume the
+ // next event, then determine what happens next by setting
+ // current state and/or the next event.
+ getState(curr_state_)->run();
+
+ // Keep going until a handler sets next event to a NOP_EVT.
+ } while (!isModelDone() && getNextEvent() != NOP_EVT);
+ } catch (const std::exception& ex) {
+ // The model has suffered an unexpected exception. This constitutes
+ // a model violation and indicates a programmatic shortcoming.
+ // In theory, the model should account for all error scenarios and
+ // deal with them accordingly. Transition to END_ST with FAILED_EVT
+ // via abortModel.
+ abortModel(ex.what());
+ }
+}
+
+void
+StateModel::nopStateHandler() {
+}
+
+void
+StateModel::initDictionaries() {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ if (dictionaries_initted_) {
+ isc_throw(StateModelError, "Dictionaries already initialized");
+ }
+ // First let's build and verify the dictionary of events.
+ try {
+ defineEvents();
+ verifyEvents();
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "Event set is invalid: " << ex.what());
+ }
+
+ // Next let's build and verify the dictionary of states.
+ try {
+ defineStates();
+ verifyStates();
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "State set is invalid: " << ex.what());
+ }
+
+ // Record that we are good to go.
+ dictionaries_initted_ = true;
+}
+
+void
+StateModel::defineEvent(unsigned int event_value, const std::string& label) {
+ if (!isModelNewInternal()) {
+ // Don't allow for self-modifying models.
+ isc_throw(StateModelError, "Events may only be added to a new model."
+ << event_value << " - " << label);
+ }
+
+ // Attempt to add the event to the set.
+ try {
+ events_.add(event_value, label);
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "Error adding event: " << ex.what());
+ }
+}
+
+const EventPtr&
+StateModel::getEvent(unsigned int event_value) {
+ if (!events_.isDefined(event_value)) {
+ isc_throw(StateModelError,
+ "Event value is not defined:" << event_value);
+ }
+
+ return (events_.get(event_value));
+}
+
+void
+StateModel::defineState(unsigned int state_value, const std::string& label,
+ StateHandler handler, const StatePausing& state_pausing) {
+ if (!isModelNewInternal()) {
+ // Don't allow for self-modifying maps.
+ isc_throw(StateModelError, "States may only be added to a new model."
+ << state_value << " - " << label);
+ }
+
+ // Attempt to add the state to the set.
+ try {
+ states_.add(state_value, label, handler, state_pausing);
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "Error adding state: " << ex.what());
+ }
+}
+
+const StatePtr
+StateModel::getState(unsigned int state_value) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return getStateInternal(state_value);
+}
+
+const StatePtr
+StateModel::getStateInternal(unsigned int state_value) {
+ if (!states_.isDefined(state_value)) {
+ isc_throw(StateModelError,
+ "State value is not defined:" << state_value);
+ }
+
+ return (states_.getState(state_value));
+}
+
+void
+StateModel::defineEvents() {
+ defineEvent(NOP_EVT, "NOP_EVT");
+ defineEvent(START_EVT, "START_EVT");
+ defineEvent(END_EVT, "END_EVT");
+ defineEvent(FAIL_EVT, "FAIL_EVT");
+}
+
+void
+StateModel::verifyEvents() {
+ getEvent(NOP_EVT);
+ getEvent(START_EVT);
+ getEvent(END_EVT);
+ getEvent(FAIL_EVT);
+}
+
+void
+StateModel::defineStates() {
+ defineState(NEW_ST, "NEW_ST",
+ std::bind(&StateModel::nopStateHandler, this));
+ defineState(END_ST, "END_ST",
+ std::bind(&StateModel::nopStateHandler, this));
+}
+
+void
+StateModel::verifyStates() {
+ getStateInternal(NEW_ST);
+ getStateInternal(END_ST);
+}
+
+void
+StateModel::onModelFailure(const std::string&) {
+ // Empty implementation to make deriving classes simpler.
+}
+
+void
+StateModel::transition(unsigned int state, unsigned int event) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ setStateInternal(state);
+ postNextEventInternal(event);
+}
+
+void
+StateModel::endModel() {
+ transition(END_ST, END_EVT);
+}
+
+void
+StateModel::unpauseModel() {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ paused_ = false;
+}
+
+void
+StateModel::abortModel(const std::string& explanation) {
+ transition(END_ST, FAIL_EVT);
+
+ std::ostringstream stream ;
+ stream << explanation << " : " << getContextStr();
+ onModelFailure(stream.str());
+}
+
+void
+StateModel::setState(unsigned int state) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ setStateInternal(state);
+}
+
+void
+StateModel::setStateInternal(unsigned int state) {
+ if (state != END_ST && !states_.isDefined(state)) {
+ isc_throw(StateModelError,
+ "Attempt to set state to an undefined value: " << state );
+ }
+
+ prev_state_ = curr_state_;
+ curr_state_ = state;
+
+ // Set the "do" flags if we are transitioning.
+ on_entry_flag_ = ((state != END_ST) && (prev_state_ != curr_state_));
+
+ // At this time they are calculated the same way.
+ on_exit_flag_ = on_entry_flag_;
+
+ // If we're entering the new state we need to see if we should
+ // pause the state model in this state.
+ if (on_entry_flag_ && !paused_ && getStateInternal(state)->shouldPause()) {
+ paused_ = true;
+ }
+}
+
+void
+StateModel::postNextEvent(unsigned int event_value) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ postNextEventInternal(event_value);
+}
+
+void
+StateModel::postNextEventInternal(unsigned int event_value) {
+ // Check for FAIL_EVT as special case of model error before events are
+ // defined.
+ if (event_value != FAIL_EVT && !events_.isDefined(event_value)) {
+ isc_throw(StateModelError,
+ "Attempt to post an undefined event, value: " << event_value);
+ }
+
+ last_event_ = next_event_;
+ next_event_ = event_value;
+}
+
+bool
+StateModel::doOnEntry() {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ bool ret = on_entry_flag_;
+ on_entry_flag_ = false;
+ return (ret);
+}
+
+bool
+StateModel::doOnExit() {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ bool ret = on_exit_flag_;
+ on_exit_flag_ = false;
+ return (ret);
+}
+
+unsigned int
+StateModel::getCurrState() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (curr_state_);
+}
+
+unsigned int
+StateModel::getPrevState() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (prev_state_);
+}
+
+unsigned int
+StateModel::getLastEvent() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (last_event_);
+}
+
+unsigned int
+StateModel::getNextEvent() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (next_event_);
+}
+
+bool
+StateModel::isModelNew() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return isModelNewInternal();
+}
+
+bool
+StateModel::isModelNewInternal() const {
+ return (curr_state_ == NEW_ST);
+}
+
+bool
+StateModel::isModelRunning() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return ((curr_state_ != NEW_ST) && (curr_state_ != END_ST));
+}
+
+bool
+StateModel::isModelWaiting() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return ((curr_state_ != NEW_ST) && (curr_state_ != END_ST) &&
+ (next_event_ == NOP_EVT));
+}
+
+bool
+StateModel::isModelDone() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (curr_state_ == END_ST);
+}
+
+bool
+StateModel::didModelFail() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return ((curr_state_ == END_ST) && (next_event_ == FAIL_EVT));
+}
+
+bool
+StateModel::isModelPaused() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (paused_);
+}
+
+std::string
+StateModel::getStateLabel(const int state) const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return getStateLabelInternal(state);
+}
+
+std::string
+StateModel::getStateLabelInternal(const int state) const {
+ return (states_.getLabel(state));
+}
+
+std::string
+StateModel::getEventLabel(const int event) const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return getEventLabelInternal(event);
+}
+
+std::string
+StateModel::getEventLabelInternal(const int event) const {
+ return (events_.getLabel(event));
+}
+
+std::string
+StateModel::getContextStr() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ std::ostringstream stream;
+ stream << "current state: [ "
+ << curr_state_ << " " << getStateLabelInternal(curr_state_)
+ << " ] next event: [ "
+ << next_event_ << " " << getEventLabelInternal(next_event_) << " ]";
+ return (stream.str());
+}
+
+std::string
+StateModel::getPrevContextStr() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ std::ostringstream stream;
+ stream << "previous state: [ "
+ << prev_state_ << " " << getStateLabelInternal(prev_state_)
+ << " ] last event: [ "
+ << next_event_ << " " << getEventLabelInternal(last_event_) << " ]";
+ return (stream.str());
+}
+
+} // namespace isc::util
+} // namespace isc
diff --git a/src/lib/util/state_model.h b/src/lib/util/state_model.h
new file mode 100644
index 0000000..6ef5238
--- /dev/null
+++ b/src/lib/util/state_model.h
@@ -0,0 +1,850 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef STATE_MODEL_H
+#define STATE_MODEL_H
+
+/// @file state_model.h This file defines the class StateModel.
+
+#include <exceptions/exceptions.h>
+#include <util/labeled_value.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <map>
+#include <mutex>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Thrown if the state machine encounters a general error.
+class StateModelError : public isc::Exception {
+public:
+ StateModelError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Define an Event.
+typedef LabeledValue Event;
+
+/// @brief Define Event pointer.
+typedef LabeledValuePtr EventPtr;
+
+/// @brief Defines a pointer to an instance method for handling a state.
+typedef std::function<void()> StateHandler;
+
+/// @brief State machine pausing modes.
+///
+/// Supported modes are:
+/// - always pause in the given state,
+/// - never pause in the given state,
+/// - pause upon first transition to the given state.
+enum StatePausing {
+ STATE_PAUSE_ALWAYS,
+ STATE_PAUSE_NEVER,
+ STATE_PAUSE_ONCE
+};
+
+/// @brief Defines a State within the State Model.
+///
+/// This class provides the means to define a state within a set or dictionary
+/// of states, and assign the state an handler method to execute the state's
+/// actions. It derives from LabeledValue which allows a set of states to be
+/// keyed by integer constants.
+///
+/// Because a state model can be paused in selected states, this class also
+/// provides the means for specifying a pausing mode and for checking whether
+/// the state model should be paused when entering this state.
+class State : public LabeledValue {
+public:
+ /// @brief Constructor
+ ///
+ /// @param value is the numeric value of the state
+ /// @param label is the text label to assign to the state
+ /// @param handler is the bound instance method which handles the state's
+ /// action.
+ /// @param state_pausing pausing mode selected for the given state. The
+ /// default value is @c STATE_PAUSE_NEVER.
+ ///
+ /// A typical invocation might look this:
+ ///
+ /// @code
+ /// State(SOME_INT_VAL, "SOME_INT_VAL",
+ /// std::bind(&StateModelDerivation::someHandler, this));
+ /// @endcode
+ ///
+ /// @throw StateModelError if label is null or blank.
+ State(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing = STATE_PAUSE_NEVER);
+
+ /// @brief Destructor
+ virtual ~State();
+
+ /// @brief Invokes the State's handler.
+ void run();
+
+ /// @brief Indicates if the state model should pause upon entering
+ /// this state.
+ ///
+ /// It modifies the @c was_paused_ flag if the state model should
+ /// pause. That way, it keeps track of visits in this particular state,
+ /// making it possible to pause only upon the first transition to the
+ /// state when @c STATE_PAUSE_ONCE mode is used.
+ bool shouldPause();
+
+private:
+ /// @brief Bound instance method pointer to the state's handler method.
+ StateHandler handler_;
+
+ /// @brief Specifies selected pausing mode for a state.
+ StatePausing pausing_;
+
+ /// @brief Indicates if the state machine was already paused in this
+ /// state.
+ bool was_paused_;
+};
+
+/// @brief Defines a shared pointer to a State.
+typedef boost::shared_ptr<State> StatePtr;
+
+/// @brief Implements a unique set or dictionary of states.
+///
+/// This class provides the means to construct and access a unique set of
+/// states. This provide the ability to validate state values, look up their
+/// text labels, and their handlers.
+class StateSet : public LabeledValueSet {
+public:
+ /// @brief Constructor
+ StateSet();
+
+ /// @brief Destructor
+ virtual ~StateSet();
+
+ /// @brief Adds a state definition to the set of states.
+ ///
+ /// @param value is the numeric value of the state
+ /// @param label is the text label to assign to the state
+ /// @param handler is the bound instance method which handles the state's
+ /// @param state_pausing state pausing mode for the given state.
+ ///
+ /// @throw StateModelError if the value is already defined in the set, or
+ /// if the label is null or blank.
+ void add(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing);
+
+ /// @brief Fetches a state for the given value.
+ ///
+ /// @param value the numeric value of the state desired
+ ///
+ /// @return A constant pointer the State found.
+ /// Note, this relies on dynamic cast and cannot return a pointer reference.
+ ///
+ /// @throw StateModelError if the value is undefined.
+ const StatePtr getState(int value);
+};
+
+/// @brief Implements a finite state machine.
+///
+/// StateModel is an abstract class that provides the structure and mechanics
+/// of a basic finite state machine.
+///
+/// The state model implementation used is a very basic approach. The model
+/// uses numeric constants to identify events and states, and maintains
+/// dictionaries of defined events and states. Event and state definitions
+/// include a text label for logging purposes. Additionally, each state
+/// definition includes a state handler. State handlers are methods which
+/// implement the actions that need to occur when the model is "in" a given
+/// state. The implementation provides methods to add entries to and verify
+/// the contents of both dictionaries.
+///
+/// During model execution, the following context is tracked:
+///
+/// * current state - The current state of the model
+/// * previous state - The state the model was in prior to the current state
+/// * next event - The next event to be consumed
+/// * last event - The event most recently consumed
+///
+/// When invoked, a state handler determines what it should do based upon the
+/// next event including what the next state and event should be. In other
+/// words the state transition knowledge is distributed among the state
+/// handlers rather than encapsulated in some form of state transition table.
+///
+/// Events "posted" from within the state handlers are "internally" triggered
+/// events. Events "posted" from outside the state model, such as through
+/// the invocation of a callback are "externally" triggered.
+///
+/// StateModel defines two states:
+///
+/// * NEW_ST - State that a model is in following instantiation. It remains in
+/// this state until model execution has begun.
+/// * END_ST - State that a model is in once it has reached its conclusion.
+///
+/// and the following events:
+///
+/// * START_EVT - Event used to start model execution.
+/// * NOP_EVT - Event used to signify that the model stop and wait for an
+/// external event, such as the completion of an asynchronous IO operation.
+/// * END_EVT - Event used to trigger a normal conclusion of the model. This
+/// means only that the model was traversed from start to finish, without any
+/// model violations (i.e. invalid state, event, or transition) or uncaught
+/// exceptions.
+/// * FAIL_EVT - Event to trigger an abnormal conclusion of the model. This
+/// event is posted internally when model execution fails due to a model
+/// violation or uncaught exception. It signifies that the model has reached
+/// an inoperable condition.
+///
+/// Derivations add their own states and events appropriate for their state
+/// model. Note that NEW_ST and END_ST do not support handlers. No work can
+/// be done (events consumed) prior to starting the model nor can work be done
+/// once the model has ended.
+///
+/// Model execution consists of iteratively invoking the state handler
+/// indicated by the current state which should consume the next event. As the
+/// handlers post events and/or change the state, the model is traversed. The
+/// loop stops whenever the model cannot continue without an externally
+/// triggered event or when it has reached its final state. In the case of
+/// the former, the loop may be re-entered upon arrival of the external event.
+///
+/// This loop is implemented in the runModel method. This method accepts an
+/// event as argument which it "posts" as the next event. It then retrieves the
+/// handler for the current state from the handler map and invokes it. runModel
+/// repeats this process until either a NOP_EVT posts or the state changes
+/// to END_ST. In other words each invocation of runModel causes the model to
+/// be traversed from the current state until it must wait or ends.
+///
+/// Re-entering the "loop" upon the occurrence of an external event is done by
+/// invoking runModel with the appropriate event. As before, runModel will
+/// loop until either the NOP_EVT occurs or until the model reaches its end.
+///
+/// A StateModel (derivation) is in the NEW_ST when constructed and remains
+/// there until it has been "started". Starting the model is done by invoking
+/// the startModel method which accepts a state as a parameter. This parameter
+/// specifies the "start" state and it becomes the current state.
+
+/// The first task undertaken by startModel is to initialize and verify the
+/// the event and state dictionaries. The following virtual methods are
+/// provided for this:
+///
+/// * defineEvents - define events
+/// * verifyEvents - verifies that the expected events are defined
+/// * defineStates - defines states
+/// * verifyStates - verifies that the expected states are defined
+///
+/// The concept behind the verify methods is to provide an initial sanity
+/// check of the dictionaries. This should help avoid using undefined event
+/// or state values accidentally.
+///
+/// These methods are intended to be implemented by each "layer" in a StateModel
+/// derivation hierarchy. This allows each layer to define additional events
+/// and states.
+///
+/// Once the dictionaries have been properly initialized, the startModel method
+/// invokes runModel with an event of START_EVT. From this point forward and
+/// until the model reaches the END_ST or fails, it is considered to be
+/// "running". If the model encounters a NOP_EVT then it is "running" and
+/// "waiting". If the model reaches END_ST with an END_EVT it is considered
+/// "done". If the model fails (END_ST with a FAILED_EVT) it is considered
+/// "done" and "failed". There are several boolean status methods which may
+/// be used to check these conditions.
+/// Once the model has been started, defining new events or new states is
+/// illegal. It is possible to call startModel only once.
+///
+/// To progress from one state to the another, state handlers invoke use
+/// the method, transition. This method accepts a state and an event as
+/// parameters. These values become the current state and the next event
+/// respectively. This has the effect of entering the given state having posted
+/// the given event. The postEvent method may be used to post a new event
+/// to the current state.
+///
+/// Bringing the model to a normal end is done by invoking the endModel method
+/// which transitions the model to END_ST with END_EVT. Bringing the model to
+/// an abnormal end is done via the abortModel method, which transitions the
+/// model to END_ST with FAILED_EVT.
+///
+/// The model can be paused in the selected states. The states in which the
+/// state model should pause (always or only once) are determined within the
+/// @c StateModel::defineStates method. The state handlers can check whether
+/// the state machine is paused or not by calling @c StateModel::isModelPaused
+/// and act accordingy. Typically, the state handler would simply post the
+/// @c NOP_EVT when it finds that the state model is paused. The model
+/// remains paused until @c StateModel::unpauseModel is called.
+class StateModel {
+public:
+
+ //@{ States common to all models.
+ /// @brief State that a state model is in immediately after construction.
+ static const int NEW_ST = 0;
+
+ /// @brief Final state, all the state model has reached its conclusion.
+ static const int END_ST = 1;
+
+ /// @brief Value at which custom states in a derived class should begin.
+ static const int SM_DERIVED_STATE_MIN = 11;
+ //@}
+
+ //@{ Events common to all state models.
+ /// @brief Signifies that no event has occurred.
+ /// This is event used to interrupt the event loop to allow waiting for
+ /// an IO event or when there is no more work to be done.
+ static const int NOP_EVT = 0;
+
+ /// @brief Event issued to start the model execution.
+ static const int START_EVT = 1;
+
+ /// @brief Event issued to end the model execution.
+ static const int END_EVT = 2;
+
+ /// @brief Event issued to abort the model execution.
+ static const int FAIL_EVT = 3;
+
+ /// @brief Value at which custom events in a derived class should begin.
+ static const int SM_DERIVED_EVENT_MIN = 11;
+ //@}
+
+ /// @brief Constructor
+ StateModel();
+
+ /// @brief Destructor
+ virtual ~StateModel();
+
+ /// @brief Begins execution of the model.
+ ///
+ /// This method invokes initDictionaries method to initialize the event
+ /// and state dictionaries and then starts the model execution setting
+ /// the current state to the given start state, and the event to START_EVT.
+ /// This method can be called only once to start the state model.
+ ///
+ /// @param start_state is the state in which to begin execution.
+ ///
+ /// @throw StateModelError or others indirectly, as this method calls
+ /// dictionary define and verify methods.
+ void startModel(const int start_state);
+
+ /// @brief Processes events through the state model
+ ///
+ /// This method implements the state model "execution loop". It uses
+ /// the given event as the next event to process and begins invoking
+ /// the state handler for the current state. As described above, the
+ /// invoked state handler consumes the next event and then determines the
+ /// next event and the current state as required to implement the business
+ /// logic. The method continues to loop until the next event posted is
+ /// NOP_EVT or the model ends.
+ ///
+ /// Any exception thrown during the loop is caught, logged, and the
+ /// model is aborted with a FAIL_EVT. The derivation's state
+ /// model is expected to account for any possible errors so any that
+ /// escape are treated as unrecoverable.
+ ///
+ /// @note This method is made virtual for the unit tests which require
+ /// customizations allowing for more control over the state model
+ /// execution.
+ ///
+ /// @param event is the next event to process
+ ///
+ /// This method is guaranteed not to throw.
+ virtual void runModel(unsigned int event);
+
+ /// @brief Conducts a normal transition to the end of the model.
+ ///
+ /// This method posts an END_EVT and sets the current state to END_ST.
+ /// It should be called by any state handler in the model from which
+ /// an exit leads to the model end. In other words, if the transition
+ /// out of a particular state is to the end of the model, that state's
+ /// handler should call endModel.
+ void endModel();
+
+ /// @brief Unpauses state model.
+ void unpauseModel();
+
+ /// @brief An empty state handler.
+ ///
+ /// This method is primarily used to permit special states, NEW_ST and
+ /// END_ST to be included in the state dictionary. Currently it is an
+ /// empty method.
+ void nopStateHandler();
+
+protected:
+
+ /// @brief Initializes the event and state dictionaries.
+ ///
+ /// This method invokes the define and verify methods for both events and
+ /// states to initialize their respective dictionaries.
+ /// This method can be called only once to initialize the state model.
+ ///
+ /// @throw StateModelError or others indirectly, as this method calls
+ /// dictionary define and verify methods.
+ void initDictionaries();
+
+ /// @brief Populates the set of events.
+ ///
+ /// This method is used to construct the set of valid events. Each class
+ /// within a StateModel derivation hierarchy uses this method to add any
+ /// events it defines to the set. Each derivation's implementation must
+ /// also call its superclass's implementation. This allows each class
+ /// within the hierarchy to make contributions to the set of defined
+ /// events. Implementations use the method, defineEvent(), to add event
+ /// definitions. An example of the derivation's implementation follows:
+ ///
+ /// @code
+ /// void StateModelDerivation::defineEvents() {
+ /// // Call the superclass implementation.
+ /// StateModelDerivation::defineEvents();
+ ///
+ /// // Add the events defined by the derivation.
+ /// defineEvent(SOME_CUSTOM_EVT_1, "CUSTOM_EVT_1");
+ /// defineEvent(SOME_CUSTOM_EVT_2, "CUSTOM_EVT_2");
+ /// :
+ /// }
+ /// @endcode
+ ///
+ /// This method is called in a thread safe context from
+ /// @ref initDictionaries.
+ virtual void defineEvents();
+
+ /// @brief Adds an event value and associated label to the set of events.
+ ///
+ /// This method is called in a thread safe context from @ref defineEvents.
+ ///
+ /// @param value is the numeric value of the event
+ /// @param label is the text label of the event used in log messages and
+ /// exceptions.
+ ///
+ /// @throw StateModelError if the model has already been started, if
+ /// the value is already defined, or if the label is empty.
+ void defineEvent(unsigned int value, const std::string& label);
+
+ /// @brief Fetches the event referred to by value.
+ ///
+ /// This method is called in a thread safe context from @ref verifyEvents.
+ ///
+ /// @param value is the numeric value of the event desired.
+ ///
+ /// @return returns a constant pointer reference to the event if found
+ ///
+ /// @throw StateModelError if the event is not defined.
+ const EventPtr& getEvent(unsigned int value);
+
+ /// @brief Validates the contents of the set of events.
+ ///
+ /// This method is invoked immediately after the defineEvents method and
+ /// is used to verify that all the required events are defined. If the
+ /// event set is determined to be invalid this method should throw a
+ /// StateModelError. As with the defineEvents method, each class within
+ /// a StateModel derivation hierarchy must supply an implementation
+ /// which calls its superclass's implementation as well as verifying any
+ /// events added by the derivation. Validating an event is accomplished
+ /// by simply attempting to fetch an event by its value from the event set.
+ /// An example of the derivation's implementation follows:
+ ///
+ /// @code
+ /// void StateModelDerivation::verifyEvents() {
+ /// // Call the superclass implementation.
+ /// StateModelDerivation::verifyEvents();
+ ///
+ /// // Verify the events defined by the derivation.
+ /// getEvent(SOME_CUSTOM_EVT_1, "CUSTOM_EVT_1");
+ /// getEvent(SOME_CUSTOM_EVT_2, "CUSTOM_EVT_2");
+ /// :
+ /// }
+ /// @endcode
+ ///
+ /// This method is called in a thread safe context from
+ /// @ref initDictionaries.
+ virtual void verifyEvents();
+
+ /// @brief Populates the set of states.
+ ///
+ /// This method is used to construct the set of valid states. Each class
+ /// within a StateModel derivation hierarchy uses this method to add any
+ /// states it defines to the set. Each derivation's implementation must
+ /// also call its superclass's implementation. This allows each class
+ /// within the hierarchy to make contributions to the set of defined
+ /// states. Implementations use the method, defineState(), to add state
+ /// definitions. An example of the derivation's implementation follows:
+ ///
+ /// @code
+ /// void StateModelDerivation::defineStates() {
+ /// // Call the superclass implementation.
+ /// StateModelDerivation::defineStates();
+ ///
+ /// // Add the states defined by the derivation.
+ /// defineState(SOME_ST, "SOME_ST",
+ /// std::bind(&StateModelDerivation::someHandler, this));
+ /// :
+ /// }
+ /// @endcode
+ ///
+ /// This method is called in a thread safe context from
+ /// @ref initDictionaries.
+ virtual void defineStates();
+
+ /// @brief Adds an state value and associated label to the set of states.
+ ///
+ /// This method is called in a thread safe context from @ref defineStates.
+ ///
+ /// @param value is the numeric value of the state
+ /// @param label is the text label of the state used in log messages and
+ /// exceptions.
+ /// @param handler is the bound instance method which implements the state's
+ /// actions.
+ /// @param state_pausing pausing mode selected for the given state. The
+ /// default value is @c STATE_PAUSE_NEVER.
+ ///
+ /// @throw StateModelError if the model has already been started, if
+ /// the value is already defined, or if the label is empty.
+ void defineState(unsigned int value, const std::string& label,
+ StateHandler handler,
+ const StatePausing& state_pausing = STATE_PAUSE_NEVER);
+
+ /// @brief Fetches the state referred to by value.
+ ///
+ /// @param value is the numeric value of the state desired.
+ ///
+ /// @return returns a constant pointer to the state if found
+ ///
+ /// @throw StateModelError if the state is not defined.
+ const StatePtr getState(unsigned int value);
+
+ /// @brief Validates the contents of the set of states.
+ ///
+ /// This method is invoked immediately after the defineStates method and
+ /// is used to verify that all the required states are defined. If the
+ /// state set is determined to be invalid this method should throw a
+ /// StateModelError. As with the defineStates method, each class within
+ /// a StateModel derivation hierarchy must supply an implementation
+ /// which calls its superclass's implementation as well as verifying any
+ /// states added by the derivation. Validating an state is accomplished
+ /// by simply attempting to fetch the state by its value from the state set.
+ /// An example of the derivation's implementation follows:
+ ///
+ /// @code
+ /// void StateModelDerivation::verifyStates() {
+ /// // Call the superclass implementation.
+ /// StateModelDerivation::verifyStates();
+ ///
+ /// // Verify the states defined by the derivation.
+ /// getState(SOME_CUSTOM_EVT_2);
+ /// :
+ /// }
+ /// @endcode
+ ///
+ /// This method is called in a thread safe context from
+ /// @ref initDictionaries.
+ virtual void verifyStates();
+
+ /// @brief Handler for fatal model execution errors.
+ ///
+ /// This method is called when an unexpected error renders during
+ /// model execution, such as a state handler throwing an exception.
+ /// It provides derivations an opportunity to act accordingly by setting
+ /// the appropriate status or taking other remedial action. This allows
+ /// the model execution loop to remain exception safe. This default
+ /// implementation does nothing.
+ ///
+ /// @param explanation text detailing the error and state machine context
+ virtual void onModelFailure(const std::string& explanation);
+
+ /// @brief Sets up the model to transition into given state with a given
+ /// event.
+ ///
+ /// This updates the model's notion of the current state and the next
+ /// event to process. State handlers use this method to move from one state
+ /// to the next.
+ ///
+ /// @param state the new value to assign to the current state.
+ /// @param event the new value to assign to the next event.
+ ///
+ /// @throw StateModelError if the state is invalid.
+ void transition(unsigned int state, unsigned int event);
+
+ /// @brief Aborts model execution.
+ ///
+ /// This method posts a FAILED_EVT and sets the current state to END_ST.
+ /// It is called internally when a model violation occurs. Violations are
+ /// any sort of inconsistency such as attempting to reference an invalid
+ /// state, or if the next event is not valid for the current state, or a
+ /// state handler throws an uncaught exception.
+ ///
+ /// @param explanation is text detailing the reason for aborting.
+ void abortModel(const std::string& explanation);
+
+ /// @brief Sets the current state to the given state value.
+ ///
+ /// This updates the model's notion of the current state and is the
+ /// state whose handler will be executed on the next iteration of the run
+ /// loop. This is intended primarily for internal use and testing. It is
+ /// unlikely that transitioning to a new state without a new event is of
+ /// much use.
+ ///
+ /// @param state the new value to assign to the current state.
+ ///
+ /// @throw StateModelError if the state is invalid.
+ void setState(unsigned int state);
+
+ /// @brief Sets the next event to the given event value.
+ ///
+ /// This updates the model's notion of the next event and is the
+ /// event that will be passed into the current state's handler on the next
+ /// iteration of the run loop.
+ ///
+ /// @param event the numeric event value to post as the next event.
+ ///
+ /// @throw StateModelError if the event is undefined
+ void postNextEvent(unsigned int event);
+
+ /// @brief Checks if on entry flag is true.
+ ///
+ /// This method acts as a one-shot test of whether or not the model is
+ /// transitioning into a new state. It returns true if the on-entry flag
+ /// is true upon entering this method and will set the flag false prior
+ /// to exit. It may be used within state handlers to perform steps that
+ /// should only occur upon entry into the state.
+ ///
+ /// @return true if the on entry flag is true, false otherwise.
+ bool doOnEntry();
+
+ /// @brief Checks if on exit flag is true.
+ ///
+ /// This method acts as a one-shot test of whether or not the model is
+ /// transitioning out of the current state. It returns true if the
+ /// on-exit flag is true upon entering this method and will set the flag
+ /// false prior to exiting. It may be used within state handlers to perform
+ /// steps that should only occur upon exiting out of the current state.
+ ///
+ /// @return true if the on entry flag is true, false otherwise.
+ bool doOnExit();
+
+public:
+
+ /// @brief Fetches the model's current state.
+ ///
+ /// This returns the model's notion of the current state. It is the
+ /// state whose handler will be executed on the next iteration of the run
+ /// loop.
+ ///
+ /// @return An unsigned int representing the current state.
+ unsigned int getCurrState() const;
+
+ /// @brief Fetches the model's previous state.
+ ///
+ /// @return An unsigned int representing the previous state.
+ unsigned int getPrevState() const;
+
+ /// @brief Fetches the model's last event.
+ ///
+ /// @return An unsigned int representing the last event.
+ unsigned int getLastEvent() const;
+
+ /// @brief Fetches the model's next event.
+ ///
+ /// This returns the model's notion of the next event. It is the
+ /// event that will be passed into the current state's handler on the next
+ /// iteration of the run loop.
+ ///
+ /// @return An unsigned int representing the next event.
+ unsigned int getNextEvent() const;
+
+ /// @brief Returns whether or not the model is new.
+ ///
+ /// @return Boolean true if the model has not been started.
+ bool isModelNew() const;
+
+ /// @brief Returns whether or not the model is running.
+ ///
+ /// @return Boolean true if the model has been started but has not yet
+ /// ended.
+ bool isModelRunning() const;
+
+ /// @brief Returns whether or not the model is waiting.
+ ///
+ /// @return Boolean true if the model is running but is waiting for an
+ /// external event for resumption.
+ bool isModelWaiting() const;
+
+ /// @brief Returns whether or not the model has finished execution.
+ ///
+ /// @return Boolean true if the model has reached the END_ST.
+ bool isModelDone() const;
+
+ /// @brief Returns whether or not the model is paused.
+ ///
+ /// @return Boolean true if the model is paused, false otherwise.
+ bool isModelPaused() const;
+
+ /// @brief Returns whether or not the model failed.
+ ///
+ /// @return Boolean true if the model has reached the END_ST and the last
+ /// event indicates a model violation, FAILED_EVT.
+ bool didModelFail() const;
+
+ /// @brief Fetches the label associated with an event value.
+ ///
+ /// @param event is the numeric event value for which the label is desired.
+ ///
+ /// @return Returns a string containing the event label or
+ /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+ std::string getEventLabel(const int event) const;
+
+ /// @brief Fetches the label associated with an state value.
+ ///
+ /// @param state is the numeric state value for which the label is desired.
+ ///
+ /// @return Returns a const char* containing the state label or
+ /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+ std::string getStateLabel(const int state) const;
+
+ /// @brief Convenience method which returns a string rendition of the
+ /// current state and next event.
+ ///
+ /// The string will be of the form:
+ ///
+ /// current state: [ {state} {label} ] next event: [ {event} {label} ]
+ ///
+ /// @return Returns a std::string of the format described above.
+ std::string getContextStr() const;
+
+ /// @brief Convenience method which returns a string rendition of the
+ /// previous state and last event.
+ ///
+ /// The string will be of the form:
+ ///
+ /// previous state: [ {state} {label} ] last event: [ {event} {label} ]
+ ///
+ /// @return Returns a std::string of the format described above.
+ std::string getPrevContextStr() const;
+
+protected:
+
+ /// @brief Fetches the state referred to by value.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param value is the numeric value of the state desired.
+ ///
+ /// @return returns a constant pointer to the state if found
+ ///
+ /// @throw StateModelError if the state is not defined.
+ const StatePtr getStateInternal(unsigned int value);
+
+private:
+
+ /// @brief Sets the current state to the given state value.
+ ///
+ /// This updates the model's notion of the current state and is the
+ /// state whose handler will be executed on the next iteration of the run
+ /// loop. This is intended primarily for internal use and testing. It is
+ /// unlikely that transitioning to a new state without a new event is of
+ /// much use.
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param state the new value to assign to the current state.
+ ///
+ /// @throw StateModelError if the state is invalid.
+ void setStateInternal(unsigned int state);
+
+ /// @brief Sets the next event to the given event value.
+ ///
+ /// This updates the model's notion of the next event and is the
+ /// event that will be passed into the current state's handler on the next
+ /// iteration of the run loop.
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param event the numeric event value to post as the next event.
+ ///
+ /// @throw StateModelError if the event is undefined
+ void postNextEventInternal(unsigned int event);
+
+ /// @brief Returns whether or not the model is new.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @return Boolean true if the model has not been started.
+ bool isModelNewInternal() const;
+
+ /// @brief Fetches the label associated with an event value.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param event is the numeric event value for which the label is desired.
+ ///
+ /// @return Returns a string containing the event label or
+ /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+ std::string getEventLabelInternal(const int event) const;
+
+ /// @brief Fetches the label associated with an state value.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param state is the numeric state value for which the label is desired.
+ ///
+ /// @return Returns a const char* containing the state label or
+ /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+ std::string getStateLabelInternal(const int state) const;
+
+ /// @brief Convenience method which returns a string rendition of the
+ /// current state and next event.
+ ///
+ /// The string will be of the form:
+ ///
+ /// current state: [ {state} {label} ] next event: [ {event} {label} ]
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @return Returns a std::string of the format described above.
+ std::string getContextStrInternal() const;
+
+ /// @brief Convenience method which returns a string rendition of the
+ /// previous state and last event.
+ ///
+ /// The string will be of the form:
+ ///
+ /// previous state: [ {state} {label} ] last event: [ {event} {label} ]
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @return Returns a std::string of the format described above.
+ std::string getPrevContextStrInternal() const;
+
+ /// @brief The dictionary of valid events.
+ LabeledValueSet events_;
+
+ /// @brief The dictionary of valid states.
+ StateSet states_;
+
+ /// @brief Indicates if the event and state dictionaries have been initted.
+ bool dictionaries_initted_;
+
+ /// @brief The current state within the model's state model.
+ unsigned int curr_state_;
+
+ /// @brief The previous state within the model's state model.
+ unsigned int prev_state_;
+
+ /// @brief The event last processed by the model.
+ unsigned int last_event_;
+
+ /// @brief The event the model should process next.
+ unsigned int next_event_;
+
+ /// @brief Indicates if state entry logic should be executed.
+ bool on_entry_flag_;
+
+ /// @brief Indicates if state exit logic should be executed.
+ bool on_exit_flag_;
+
+ /// @brief Indicates if the state model is paused.
+ bool paused_;
+
+ /// @brief Protects against concurrent transitions.
+ boost::shared_ptr<std::mutex> mutex_;
+};
+
+/// @brief Defines a pointer to a StateModel.
+typedef boost::shared_ptr<StateModel> StateModelPtr;
+
+} // namespace isc::util
+} // namespace isc
+#endif
diff --git a/src/lib/util/stopwatch.cc b/src/lib/util/stopwatch.cc
new file mode 100644
index 0000000..f75c6cd
--- /dev/null
+++ b/src/lib/util/stopwatch.cc
@@ -0,0 +1,85 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/stopwatch.h>
+#include <util/stopwatch_impl.h>
+
+namespace isc {
+namespace util {
+
+using namespace boost::posix_time;
+
+Stopwatch::Stopwatch(const bool autostart)
+ : impl_(new StopwatchImpl()) {
+ // If the autostart has been specified, invoke start.
+ if (autostart) {
+ start();
+ }
+}
+
+Stopwatch::~Stopwatch() {
+ delete impl_;
+}
+
+void
+Stopwatch::start() {
+ impl_->start();
+}
+
+void
+Stopwatch::stop() {
+ impl_->stop();
+}
+
+void
+Stopwatch::reset() {
+ impl_->reset();
+}
+
+boost::posix_time::time_duration
+Stopwatch::getLastDuration() const {
+ return (impl_->getLastDuration());
+}
+
+boost::posix_time::time_duration
+Stopwatch::getTotalDuration() const {
+ return (impl_->getTotalDuration());
+}
+
+long
+Stopwatch::getLastMilliseconds() const {
+ return (getLastDuration().total_milliseconds());
+}
+
+long
+Stopwatch::getTotalMilliseconds() const {
+ return (getTotalDuration().total_milliseconds());
+}
+
+long
+Stopwatch::getLastMicroseconds() const {
+ return (getLastDuration().total_microseconds());
+}
+
+long
+Stopwatch::getTotalMicroseconds() const {
+ return (getTotalDuration().total_microseconds());
+}
+
+std::string
+Stopwatch::logFormatLastDuration() const {
+ return (StopwatchImpl::logFormat(getLastDuration()));
+}
+
+std::string
+Stopwatch::logFormatTotalDuration() const {
+ return (StopwatchImpl::logFormat(getTotalDuration()));
+}
+
+} // end of isc::util
+} // end of isc
diff --git a/src/lib/util/stopwatch.h b/src/lib/util/stopwatch.h
new file mode 100644
index 0000000..c3d0b23
--- /dev/null
+++ b/src/lib/util/stopwatch.h
@@ -0,0 +1,129 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef STOPWATCH_H
+#define STOPWATCH_H
+
+#include <boost/noncopyable.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief Forward declaration to the @c Stopwatch implementation.
+class StopwatchImpl;
+
+/// @brief Utility class to measure code execution times.
+///
+/// The API of this class is based on the use cases of a stopwatch. It is
+/// used to measure time spent executing portions of the code. The typical
+/// use case for the @c Stopwatch is to measure the time spent invoking
+/// callouts in hooks library. This provides means for diagnosing the
+/// server's performance degradations when hooks libraries are in use.
+///
+/// This class exposes functions like @c start, @c stop and @c reset which
+/// behave in the same way as a stopwatch used to measure time for sport
+/// activities.
+///
+/// It is possible to measure the cumulative execution time by invoking
+/// @c start and @c stop consecutively. The total measured time will be
+/// a sum of durations between the invocations of respective starts and
+/// stops.
+class Stopwatch : boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param autostart Indicates if the stopwatch should be initialized to
+ /// the "started" state. In this state the stopwatch is measuring the time
+ /// since it has been started (object has been constructed in this case.
+ /// If the parameter is set to false (default value), the
+ /// @c Stopwatch::start must be called to start time measurement.
+ Stopwatch(const bool autostart = true);
+
+ /// @brief Destructor.
+ ///
+ /// Destroys the implementation instance.
+ ~Stopwatch();
+
+ /// @brief Starts the stopwatch.
+ ///
+ /// Sets the stopwatch to the "started" state. In this state the stopwatch
+ /// is measuring the duration since @c Stopwatch::start has been invoked.
+ ///
+ //// This method is no-op if the stopwatch is already in the "started"
+ /// state.
+ void start();
+
+ /// @brief Stops the stopwatch.
+ ///
+ /// Sets the stopwatch to the "stopped" state. The stopwatch stops the time
+ /// measurement and records the duration between the last stopwatch start
+ /// and the stop invocation. It also updates the total measured duration,
+ /// i.e. the sum of durations between all start/stop invocations. Both
+ /// values can be retrieved using @c Stopwatch::getLastDuration and
+ /// @c Stopwatch::getTotalDuration respectively, or their variants.
+ ///
+ /// This method is no-op if the stopwatch is already in the "stopped" state.
+ void stop();
+
+ /// @brief Resets the stopwatch.
+ ///
+ /// It resets the stopwatch to the initial state. In this state, the last
+ /// measured duration and the total duration is set to 0. The stopwatch
+ /// is set to the "stopped" state.
+ void reset();
+
+ /// @brief Retrieves last measured duration.
+ ///
+ /// If the stopwatch is in the "stopped" state this method retrieves the
+ /// duration between the last start and stop. If the stopwatch is in the
+ /// "started" state, the retrieved duration is the duration between the
+ /// last start of the stopwatch and the current time.
+ boost::posix_time::time_duration getLastDuration() const;
+
+ /// @brief Retrieves total measured duration.
+ ///
+ /// If the stopwatch is in the "stopped" state this method retrieves the
+ /// total duration between all starts and stops invoked during the
+ /// lifetime of the object or since the last reset. If the stopwatch is
+ /// in the "started" state, the returned is the sum of all durations
+ /// between respective starts and stops, and the duration since the
+ /// stopwatch has been last started and the current time.
+ boost::posix_time::time_duration getTotalDuration() const;
+
+ /// @brief Retrieves the last measured duration in milliseconds.
+ long getLastMilliseconds() const;
+
+ /// @brief Retrieves the total measured duration in milliseconds.
+ long getTotalMilliseconds() const;
+
+ /// @brief Retrieves the last measured duration in microseconds.
+ long getLastMicroseconds() const;
+
+ /// @brief Retrieves the total measured duration in microseconds.
+ long getTotalMicroseconds() const;
+
+ /// @brief Returns the last measured duration in the format directly
+ /// usable in log messages.
+ std::string logFormatLastDuration() const;
+
+ /// @brief Returns the total measured duration in the format directly
+ /// usable in the log messages.
+ std::string logFormatTotalDuration() const;
+
+private:
+
+ /// @brief Pointer to the @c StopwatchImpl.
+ StopwatchImpl* impl_;
+
+};
+
+}
+}
+
+#endif // STOPWATCH_H
+
diff --git a/src/lib/util/stopwatch_impl.cc b/src/lib/util/stopwatch_impl.cc
new file mode 100644
index 0000000..12c154a
--- /dev/null
+++ b/src/lib/util/stopwatch_impl.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/stopwatch_impl.h>
+#include <iomanip>
+#include <sstream>
+
+namespace isc {
+namespace util {
+
+using namespace boost::posix_time;
+
+StopwatchImpl::StopwatchImpl()
+ : started_(false),
+ last_start_(getCurrentTime()),
+ last_stop_(last_start_),
+ cumulative_time_(microseconds(0)) {
+}
+
+StopwatchImpl::~StopwatchImpl() {
+}
+
+void
+StopwatchImpl::start() {
+ // If stopwatch is "stopped", start it.
+ if (!started_) {
+ last_start_ = getCurrentTime();
+ started_ = true;
+ }
+}
+
+void
+StopwatchImpl::stop() {
+ // Is stopwatch is "started", stop it.
+ if (started_) {
+ last_stop_ = getCurrentTime();
+ // Update the total time with the last measured duration.
+ cumulative_time_ += last_stop_ - last_start_;
+ started_ = false;
+ }
+}
+
+void
+StopwatchImpl::reset() {
+ // Set last start and stop values to the current time. This is the
+ // same as in the constructor. As a result the last duration will
+ // be 0.
+ last_start_ = getCurrentTime();
+ last_stop_ = last_start_;
+ // Set the total duration to 0.
+ cumulative_time_ = microseconds(0);
+ started_ = false;
+}
+
+time_duration
+StopwatchImpl::getLastDuration() const {
+ // If the stopwatch is started, the time measured is between the
+ // start time and the current time. Otherwise, it is between the
+ // start time and last stop.
+ ptime end_time = started_ ? getCurrentTime() : last_stop_;
+ return (end_time - last_start_);
+}
+
+time_duration
+StopwatchImpl::getTotalDuration() const {
+ // Get the total time recorded so far.
+ time_duration total_duration = cumulative_time_;
+ if (started_) {
+ // If the stopwatch is started, add the duration between the
+ // start time and current time.
+ total_duration += (getCurrentTime() - last_start_);
+ }
+ return (total_duration);
+}
+
+std::string
+StopwatchImpl::logFormat(const boost::posix_time::time_duration& duration) {
+ std::ostringstream s;
+ if (duration.total_seconds() > 0) {
+ s << duration.total_seconds() << "."
+ << std::setfill('0') << std::setw(2) << (duration.total_milliseconds()/10 % 100)
+ << " s";
+ } else {
+ s << duration.total_milliseconds() << ".";
+ s << std::setfill('0') << std::setw(3) << (duration.total_microseconds() % 1000)
+ << " ms";
+ }
+ return (s.str());
+}
+
+ptime
+StopwatchImpl::getCurrentTime() const {
+ return (microsec_clock::universal_time());
+}
+
+
+} // end of isc::util
+} // end of isc
diff --git a/src/lib/util/stopwatch_impl.h b/src/lib/util/stopwatch_impl.h
new file mode 100644
index 0000000..0aaae62
--- /dev/null
+++ b/src/lib/util/stopwatch_impl.h
@@ -0,0 +1,122 @@
+// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef STOPWATCH_IMPL_H
+#define STOPWATCH_IMPL_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief @c Stopwatch class implementation.
+///
+/// The @c Stopwatch class uses the plimpl idiom to make it easier to unit
+/// test behavior of the @c Stopwatch class without a need to rely on the system
+/// clock. The @c StopwatchImpl API allows for overriding the @c getCurrentTime
+/// method to return the arbitrary time value as current time to various
+/// methods. By setting the current time to arbitrary values the test can expect
+/// arbitrary values being returned by the class methods.
+///
+/// Also, by using the pimpl idiom the @c Stopwatch class hides its implementation
+/// details and leaves the header with only the pointer to the @c StopwatchImpl
+/// class.
+class StopwatchImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes the internally used timestamps. It also sets the state of
+ /// the stopwatch to "stopped".
+ StopwatchImpl();
+
+ /// @brief Virtual destructor.
+ ///
+ /// This destructor is virtual because the @c StopwatchImpl::getCurrentTime
+ /// is virtual.
+ virtual ~StopwatchImpl();
+
+ /// @brief Starts the stopwatch.
+ ///
+ /// Sets the stopwatch to the "started" state. It also records the time when
+ /// the stopwatch is started. This method is no-op if the stopwatch is
+ /// already in the "started" state.
+ ///
+ /// Also see the @c Stopwatch::start for details.
+ void start();
+
+ /// @brief Stop the stopwatch.
+ ///
+ /// Sets the stopwatch to the "stopped" state. The stop time is recorded and
+ /// the cumulative time is updated to include the duration between the most
+ /// recent start and stop. This method is no-op if the stopwatch is already
+ /// in the "stopped" state.
+ ///
+ /// Also see the @c Stopwatch::stop for details.
+ void stop();
+
+ /// @brief Reset the stopwatch.
+ ///
+ /// Also see the @c Stopwatch::reset for details.
+ void reset();
+
+ /// @brief Retrieves the measured duration.
+ ///
+ /// Also see the @c Stopwatch::getLastDuration for details.
+ boost::posix_time::time_duration getLastDuration() const;
+
+ /// @brief Retrieves the total measured duration.
+ ///
+ /// Also see the @c Stopwatch::getTotalDuration for details.
+ boost::posix_time::time_duration getTotalDuration() const;
+
+ /// @brief Returns the duration in the textual format which can be
+ /// directly used in log messages.
+ ///
+ /// @param duration Duration to be converted to the textual format.
+ ///
+ /// @return Converted value which can be used to log duration.
+ static std::string
+ logFormat(const boost::posix_time::time_duration& duration);
+
+protected:
+
+ /// @brief Returns the current time.
+ ///
+ /// This method is used internally by the @c StopwatchImpl class and
+ /// its derivations. This class simply returns the value of
+ /// @c boost::posix_time::microsec_clock::univeral_time(), which is
+ /// a current timestamp. The derivations may replace it with the
+ /// custom implementations. The typical use case is for the unit tests
+ /// to customize the behavior of this function to return well known
+ /// (deterministic) values. As a result, it is possible to influence
+ /// the "measured" values returned by accessors of this class, which
+ /// can be compared against some exact values.
+ virtual boost::posix_time::ptime getCurrentTime() const;
+
+private:
+
+ /// @brief Holds the state of the stopwatch.
+ bool started_;
+
+ /// @brief Holds the timestamp when the stopwatch has been last started.
+ boost::posix_time::ptime last_start_;
+
+ /// @brief Holds the timestamp when the stopwatch has been last stopped.
+ boost::posix_time::ptime last_stop_;
+
+ /// @brief Holds the total measured time since the stopwatch has been
+ /// first started after creation or reset.
+ boost::posix_time::time_duration cumulative_time_;
+
+};
+
+}
+}
+
+#endif // STOPWATCH_H
+
diff --git a/src/lib/util/strutil.cc b/src/lib/util/strutil.cc
new file mode 100644
index 0000000..55f5f97
--- /dev/null
+++ b/src/lib/util/strutil.cc
@@ -0,0 +1,467 @@
+// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+#include <numeric>
+#include <iostream>
+#include <sstream>
+
+// Early versions of C++11 regex were buggy, use it if we
+// can otherwise, we fall back to regcomp/regexec. For more info see:
+// https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions
+#ifdef USE_REGEX
+#include <regex>
+#else
+#include <sys/types.h>
+#include <regex.h>
+#endif
+
+#include <string.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace str {
+
+// Normalize slashes
+
+void
+normalizeSlash(std::string& name) {
+ if (!name.empty()) {
+ size_t pos = 0;
+ while ((pos = name.find('\\', pos)) != std::string::npos) {
+ name[pos] = '/';
+ }
+ }
+}
+
+// Trim String
+
+string
+trim(const string& instring) {
+ string retstring = "";
+ if (!instring.empty()) {
+ static const char* blanks = " \t\n";
+
+ // Search for first non-blank character in the string
+ size_t first = instring.find_first_not_of(blanks);
+ if (first != string::npos) {
+
+ // String not all blanks, so look for last character
+ size_t last = instring.find_last_not_of(blanks);
+
+ // Extract the trimmed substring
+ retstring = instring.substr(first, (last - first + 1));
+ }
+ }
+
+ return (retstring);
+}
+
+// Tokenize string. As noted in the header, this is locally written to avoid
+// another dependency on a Boost library.
+
+vector<string>
+tokens(const std::string& text, const std::string& delim, bool escape) {
+ vector<string> result;
+ string token;
+ bool in_token = false;
+ bool escaped = false;
+ for (auto c = text.cbegin(); c != text.cend(); ++c) {
+ if (delim.find(*c) != string::npos) {
+ // Current character is a delimiter
+ if (!in_token) {
+ // Two or more delimiters, eat them
+ } else if (escaped) {
+ // Escaped delimiter in a token: reset escaped and keep it
+ escaped = false;
+ token.push_back(*c);
+ } else {
+ // End of the current token: save it if not empty
+ if (!token.empty()) {
+ result.push_back(token);
+ }
+ // Reset state
+ in_token = false;
+ token.clear();
+ }
+ } else if (escape && (*c == '\\')) {
+ // Current character is the escape character
+ if (!in_token) {
+ // The escape character is the first character of a new token
+ in_token = true;
+ }
+ if (escaped) {
+ // Escaped escape: reset escaped and keep one character
+ escaped = false;
+ token.push_back(*c);
+ } else {
+ // Remember to keep the next character
+ escaped = true;
+ }
+ } else {
+ // Not a delimiter nor an escape
+ if (!in_token) {
+ // First character of a new token
+ in_token = true;
+ }
+ if (escaped) {
+ // Escaped common character: as escape was false
+ escaped = false;
+ token.push_back('\\');
+ token.push_back(*c);
+ } else {
+ // The common case: keep it
+ token.push_back(*c);
+ }
+ }
+ }
+ // End of input: close and save the current token if not empty
+ if (escaped) {
+ // Pending escape
+ token.push_back('\\');
+ }
+ if (!token.empty()) {
+ result.push_back(token);
+ }
+
+ return (result);
+}
+
+// Local function to pass to accumulate() for summing up string lengths.
+
+namespace {
+
+size_t
+lengthSum(string::size_type curlen, const string& cur_string) {
+ return (curlen + cur_string.size());
+}
+
+}
+
+// Provide printf-style formatting.
+
+std::string
+format(const std::string& format, const std::vector<std::string>& args) {
+
+ static const string flag = "%s";
+
+ // Initialize return string. To speed things up, we'll reserve an
+ // appropriate amount of space - current string size, plus length of all
+ // the argument strings, less two characters for each argument (the %s in
+ // the format string is being replaced).
+ string result;
+ size_t length = accumulate(args.begin(), args.end(), format.size(),
+ lengthSum) - (args.size() * flag.size());
+ result.reserve(length);
+
+ // Iterate through replacing all tokens
+ result = format;
+ size_t tokenpos = 0; // Position of last token replaced
+ std::vector<std::string>::size_type i = 0; // Index into argument array
+
+ while ((i < args.size()) && (tokenpos != string::npos)) {
+ tokenpos = result.find(flag, tokenpos);
+ if (tokenpos != string::npos) {
+ result.replace(tokenpos, flag.size(), args[i++]);
+ }
+ }
+
+ return (result);
+}
+
+std::string
+getToken(std::istringstream& iss) {
+ string token;
+ iss >> token;
+ if (iss.bad() || iss.fail()) {
+ isc_throw(StringTokenError, "could not read token from string");
+ }
+ return (token);
+}
+
+std::vector<uint8_t>
+quotedStringToBinary(const std::string& quoted_string) {
+ std::vector<uint8_t> binary;
+ // Remove whitespace before and after the quotes.
+ std::string trimmed_string = trim(quoted_string);
+
+ // We require two quote characters, so the length of the string must be
+ // equal to 2 at minimum, and it must start and end with quotes.
+ if ((trimmed_string.length() > 1) && ((trimmed_string[0] == '\'') &&
+ (trimmed_string[trimmed_string.length()-1] == '\''))) {
+ // Remove quotes and trim the text inside the quotes.
+ trimmed_string = trim(trimmed_string.substr(1, trimmed_string.length() - 2));
+ // Copy string contents into the vector.
+ binary.assign(trimmed_string.begin(), trimmed_string.end());
+ }
+ // Return resulting vector or empty vector.
+ return (binary);
+}
+
+void
+decodeColonSeparatedHexString(const std::string& hex_string,
+ std::vector<uint8_t>& binary) {
+ decodeSeparatedHexString(hex_string, ":", binary);
+}
+
+void
+decodeSeparatedHexString(const std::string& hex_string, const std::string& sep,
+ std::vector<uint8_t>& binary) {
+ std::vector<std::string> split_text;
+ boost::split(split_text, hex_string, boost::is_any_of(sep),
+ boost::algorithm::token_compress_off);
+
+ std::vector<uint8_t> binary_vec;
+ for (size_t i = 0; i < split_text.size(); ++i) {
+
+ // If there are multiple tokens and the current one is empty, it
+ // means that two consecutive colons were specified. This is not
+ // allowed.
+ if ((split_text.size() > 1) && split_text[i].empty()) {
+ isc_throw(isc::BadValue, "two consecutive separators ('" << sep << "') specified in"
+ " a decoded string '" << hex_string << "'");
+
+ // Between a colon we expect at most two characters.
+ } else if (split_text[i].size() > 2) {
+ isc_throw(isc::BadValue, "invalid format of the decoded string"
+ << " '" << hex_string << "'");
+
+ } else if (!split_text[i].empty()) {
+ std::stringstream s;
+ s << "0x";
+
+ for (unsigned int j = 0; j < split_text[i].length(); ++j) {
+ // Check if we're dealing with hexadecimal digit.
+ if (!isxdigit(split_text[i][j])) {
+ isc_throw(isc::BadValue, "'" << split_text[i][j]
+ << "' is not a valid hexadecimal digit in"
+ << " decoded string '" << hex_string << "'");
+ }
+ s << split_text[i][j];
+ }
+
+ // The stream should now have one or two hexadecimal digits.
+ // Let's convert it to a number and store in a temporary
+ // vector.
+ unsigned int binary_value;
+ s >> std::hex >> binary_value;
+
+ binary_vec.push_back(static_cast<uint8_t>(binary_value));
+ }
+
+ }
+
+ // All ok, replace the data in the output vector with a result.
+ binary.swap(binary_vec);
+}
+
+
+void
+decodeFormattedHexString(const std::string& hex_string,
+ std::vector<uint8_t>& binary) {
+ // If there is at least one colon we assume that the string
+ // comprises octets separated by colons (e.g. MAC address notation).
+ if (hex_string.find(':') != std::string::npos) {
+ decodeSeparatedHexString(hex_string, ":", binary);
+ } else if (hex_string.find(' ') != std::string::npos) {
+ decodeSeparatedHexString(hex_string, " ", binary);
+ } else {
+ std::ostringstream s;
+
+ // If we have odd number of digits we'll have to prepend '0'.
+ if (hex_string.length() % 2 != 0) {
+ s << "0";
+ }
+
+ // It is ok to use '0x' prefix in a string.
+ if ((hex_string.length() > 2) && (hex_string.substr(0, 2) == "0x")) {
+ // Exclude '0x' from the decoded string.
+ s << hex_string.substr(2);
+
+ } else {
+ // No '0x', so decode the whole string.
+ s << hex_string;
+ }
+
+ try {
+ // Decode the hex string.
+ encode::decodeHex(s.str(), binary);
+
+ } catch (...) {
+ isc_throw(isc::BadValue, "'" << hex_string << "' is not a valid"
+ " string of hexadecimal digits");
+ }
+ }
+}
+
+class StringSanitizerImpl {
+public:
+ /// @brief Constructor.
+ StringSanitizerImpl(const std::string& char_set, const std::string& char_replacement)
+ : char_set_(char_set), char_replacement_(char_replacement) {
+ if (char_set.size() > StringSanitizer::MAX_DATA_SIZE) {
+ isc_throw(isc::BadValue, "char set size: '" << char_set.size()
+ << "' exceeds max size: '"
+ << StringSanitizer::MAX_DATA_SIZE << "'");
+ }
+
+ if (char_replacement.size() > StringSanitizer::MAX_DATA_SIZE) {
+ isc_throw(isc::BadValue, "char replacement size: '"
+ << char_replacement.size() << "' exceeds max size: '"
+ << StringSanitizer::MAX_DATA_SIZE << "'");
+ }
+#ifdef USE_REGEX
+ try {
+ scrub_exp_ = std::regex(char_set, std::regex::extended);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "invalid regex: '"
+ << char_set_ << "', " << ex.what());
+ }
+#else
+ int ec = regcomp(&scrub_exp_, char_set_.c_str(), REG_EXTENDED);
+ if (ec) {
+ char errbuf[512] = "";
+ static_cast<void>(regerror(ec, &scrub_exp_, errbuf, sizeof(errbuf)));
+ regfree(&scrub_exp_);
+ isc_throw(isc::BadValue, "invalid regex: '" << char_set_ << "', " << errbuf);
+ }
+#endif
+ }
+
+ /// @brief Destructor.
+ ~StringSanitizerImpl() {
+#ifndef USE_REGEX
+ regfree(&scrub_exp_);
+#endif
+ }
+
+ std::string scrub(const std::string& original) {
+#ifdef USE_REGEX
+ std::stringstream result;
+ try {
+ std::regex_replace(std::ostream_iterator<char>(result),
+ original.begin(), original.end(),
+ scrub_exp_, char_replacement_);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "replacing '" << char_set_ << "' with '"
+ << char_replacement_ << "' in '" << original << "' failed: ,"
+ << ex.what());
+ }
+
+ return (result.str());
+#else
+ // In order to handle embedded nuls, we have to process in nul-terminated
+ // chunks. We iterate over the original data, doing pattern replacement
+ // on each chunk.
+ const char* orig_data = original.data();
+ const char* dead_end = orig_data + original.size();
+ const char* start_from = orig_data;
+ stringstream result;
+
+ while (start_from < dead_end) {
+ // Iterate over original string, match by match.
+ regmatch_t matches[2]; // n matches + 1
+ const char* end_at = start_from + strlen(start_from);
+
+ while (start_from < end_at) {
+ // Look for the next match
+ if (regexec(&scrub_exp_, start_from, 1, matches, 0) == REG_NOMATCH) {
+ // No matches, so add in the remainder
+ result << start_from;
+ start_from = end_at + 1;
+ break;
+ }
+
+ // Shouldn't happen, but one never knows eh?
+ if (matches[0].rm_so == -1) {
+ isc_throw(isc::Unexpected, "matched but so is -1?");
+ }
+
+ // Add everything from starting point up to the current match
+ const char* match_at = start_from + matches[0].rm_so;
+ while (start_from < match_at) {
+ result << *start_from;
+ ++start_from;
+ }
+
+ // Add in the replacement
+ result << char_replacement_;
+
+ // Move past the match.
+ ++start_from;
+ }
+
+ // if we have an embedded nul, replace it and continue
+ if (start_from < dead_end) {
+ // Add in the replacement
+ result << char_replacement_;
+ start_from = end_at + 1;
+ }
+ }
+
+ return (result.str());
+#endif
+ }
+
+private:
+ /// @brief The char set data for regex.
+ std::string char_set_;
+
+ /// @brief The char replacement data for regex.
+ std::string char_replacement_;
+
+#ifdef USE_REGEX
+ regex scrub_exp_;
+#else
+ regex_t scrub_exp_;
+#endif
+};
+
+// @note The regex engine is implemented using recursion and can cause
+// stack overflow if the input data is too large. An arbitrary size of
+// 4096 should be enough for all cases.
+const uint32_t StringSanitizer::MAX_DATA_SIZE = 4096;
+
+StringSanitizer::StringSanitizer(const std::string& char_set,
+ const std::string& char_replacement)
+ : impl_(new StringSanitizerImpl(char_set, char_replacement)) {
+}
+
+StringSanitizer::~StringSanitizer() {
+}
+
+std::string
+StringSanitizer::scrub(const std::string& original) {
+ return (impl_->scrub(original));
+}
+
+std::string dumpAsHex(const uint8_t* data, size_t length) {
+ std::stringstream output;
+ for (unsigned int i = 0; i < length; i++) {
+ if (i) {
+ output << ":";
+ }
+
+ output << std::setfill('0') << std::setw(2) << std::hex
+ << static_cast<unsigned short>(data[i]);
+ }
+
+ return (output.str());
+}
+
+} // namespace str
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/strutil.h b/src/lib/util/strutil.h
new file mode 100644
index 0000000..e5d2496
--- /dev/null
+++ b/src/lib/util/strutil.h
@@ -0,0 +1,403 @@
+// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef STRUTIL_H
+#define STRUTIL_H
+
+#include <algorithm>
+#include <cctype>
+#include <stdint.h>
+#include <string>
+#include <iomanip>
+#include <sstream>
+#include <vector>
+#include <exceptions/exceptions.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace util {
+namespace str {
+
+/// @brief A Set of C++ Utilities for Manipulating Strings
+
+///
+/// @brief A standard string util exception that is thrown if getToken or
+/// numToToken are called with bad input data
+///
+class StringTokenError : public Exception {
+public:
+ StringTokenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Normalize Backslash
+///
+/// Only relevant to Windows, this replaces all "\" in a string with "/"
+/// and returns the result. On other systems it is a no-op. Note
+/// that Windows does recognize file names with the "\" replaced by "/"
+/// (at least in system calls, if not the command line).
+///
+/// @param name Name to be substituted
+void normalizeSlash(std::string& name);
+
+/// @brief Trim Leading and Trailing Spaces
+///
+/// Returns a copy of the input string but with any leading or trailing spaces
+/// or tabs removed.
+///
+/// @param instring Input string to modify
+///
+/// @return String with leading and trailing spaces removed
+std::string trim(const std::string& instring);
+
+/// @brief Finds the "trimmed" end of a buffer
+///
+/// Works backward from the end of the buffer, looking for the first
+/// character not equal to the trim value, and returns an iterator
+/// pointing that that position.
+///
+/// @param begin - Forward iterator pointing to the beginning of the
+/// buffer to trim
+/// @param end - Forward iterator pointing to the untrimmed end of
+/// the buffer to trim
+/// @param trim_val - byte value to trim off
+///
+/// @return Iterator pointing the first character from the end of the
+/// buffer not equal to the trim value
+template<typename Iterator>
+Iterator
+seekTrimmed(Iterator begin, Iterator end, uint8_t trim_val) {
+ for ( ; end != begin && *(end - 1) == trim_val; --end);
+ return(end);
+}
+
+/// @brief Split String into Tokens
+///
+/// Splits a string into tokens (the tokens being delimited by one or more of
+/// the delimiter characters) and returns the tokens in a vector array. Note
+/// that adjacent delimiters are considered to be a single delimiter.
+///
+/// Special cases are:
+/// -# The empty string is considered to be zero tokens.
+/// -# A string comprising nothing but delimiters is considered to be zero
+/// tokens.
+///
+/// The reasoning behind this is that the string can be thought of as having
+/// invisible leading and trailing delimiter characters. Therefore both cases
+/// reduce to a set of contiguous delimiters, which are considered a single
+/// delimiter (so getting rid of the string).
+/// Optional escape allows to escape delimiter characters (and *only* them
+/// and the escape character itself) using backslash.
+///
+/// We could use Boost for this, but this (simple) function eliminates one
+/// dependency in the code.
+///
+/// @param text String to be split. Passed by value as the internal copy is
+/// altered during the processing.
+/// @param delim Delimiter characters
+/// @param escape Use backslash to escape delimiter characters
+///
+/// @return Vector of tokens.
+std::vector<std::string> tokens(const std::string& text,
+ const std::string& delim = std::string(" \t\n"),
+ bool escape = false);
+
+/// @brief Uppercase Character
+///
+/// Used in uppercase() to pass as an argument to std::transform(). The
+/// function std::toupper() can't be used as it takes an "int" as its argument;
+/// this confuses the template expansion mechanism because dereferencing a
+/// string::iterator returns a char.
+///
+/// @param chr Character to be upper-cased.
+///
+/// @return Uppercase version of the argument
+inline char toUpper(char chr) {
+ return (static_cast<char>(std::toupper(static_cast<int>(chr))));
+}
+
+/// @brief Uppercase String
+///
+/// A convenience function to uppercase a string.
+///
+/// @param text String to be upper-cased.
+inline void uppercase(std::string& text) {
+ std::transform(text.begin(), text.end(), text.begin(),
+ isc::util::str::toUpper);
+}
+
+/// @brief Lowercase Character
+///
+/// Used in lowercase() to pass as an argument to std::transform(). The
+/// function std::tolower() can't be used as it takes an "int" as its argument;
+/// this confuses the template expansion mechanism because dereferencing a
+/// string::iterator returns a char.
+///
+/// @param chr Character to be lower-cased.
+///
+/// @return Lowercase version of the argument
+inline char toLower(char chr) {
+ return (static_cast<char>(std::tolower(static_cast<int>(chr))));
+}
+
+/// @brief Lowercase String
+///
+/// A convenience function to lowercase a string
+///
+/// @param text String to be lower-cased.
+inline void lowercase(std::string& text) {
+ std::transform(text.begin(), text.end(), text.begin(),
+ isc::util::str::toLower);
+}
+
+/// @brief Apply Formatting
+///
+/// Given a printf-style format string containing only "%s" place holders
+/// (others are ignored) and a vector of strings, this produces a single string
+/// with the placeholders replaced.
+///
+/// @param format Format string
+/// @param args Vector of argument strings
+///
+/// @return Resultant string
+std::string format(const std::string& format,
+ const std::vector<std::string>& args);
+
+
+/// @brief Returns one token from the given stringstream
+///
+/// Using the >> operator, with basic error checking
+///
+/// @throw StringTokenError if the token cannot be read from the stream
+///
+/// @param iss stringstream to read one token from
+///
+/// @return the first token read from the stringstream
+std::string getToken(std::istringstream& iss);
+
+/// @brief Converts a string token to an *unsigned* integer.
+///
+/// The value is converted using a lexical cast, with error and bounds
+/// checking.
+///
+/// NumType is a *signed* integral type (e.g. int32_t) that is sufficiently
+/// wide to store resulting integers.
+///
+/// BitSize is the maximum number of bits that the resulting integer can take.
+/// This function first checks whether the given token can be converted to
+/// an integer of NumType type. It then confirms the conversion result is
+/// within the valid range, i.e., [0, 2^BitSize - 1]. The second check is
+/// necessary because lexical_cast<T> where T is an unsigned integer type
+/// doesn't correctly reject negative numbers when compiled with SunStudio.
+///
+/// @throw StringTokenError if the value is out of range, or if it
+/// could not be converted
+///
+/// @param num_token the string token to convert
+///
+/// @return the converted value, of type NumType
+template <typename NumType, int BitSize>
+NumType
+tokenToNum(const std::string& num_token) {
+ NumType num;
+ try {
+ num = boost::lexical_cast<NumType>(num_token);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(StringTokenError, "Invalid SRV numeric parameter: " <<
+ num_token);
+ }
+ if (num < 0 || num >= (static_cast<NumType>(1) << BitSize)) {
+ isc_throw(StringTokenError, "Numeric SRV parameter out of range: " <<
+ num);
+ }
+ return (num);
+}
+
+/// @brief Converts a string in quotes into vector.
+///
+/// A converted string is first trimmed. If a trimmed string is in
+/// quotes, the quotes are removed and the resulting string is copied
+/// into a vector. If the string is not in quotes, an empty vector is
+/// returned.
+///
+/// The resulting string is copied to a vector and returned.
+///
+/// This function is intended to be used by the server configuration
+/// parsers to convert string values surrounded with quotes into
+/// binary form.
+///
+/// @param quoted_string String to be converted.
+///
+/// @return Vector containing converted string or empty string if
+/// input string didn't contain expected quote characters.
+std::vector<uint8_t>
+quotedStringToBinary(const std::string& quoted_string);
+
+/// @brief Converts a string of separated hexadecimal digits
+/// into a vector.
+///
+/// Octets may contain 1 or 2 digits. For example, using a colon
+/// for a separator all of the following are valid:
+///
+/// - yy:yy:yy:yy:yy
+/// - y:y:y:y:y
+/// - y:yy:yy:y:y
+///
+/// If the decoded string doesn't match any of the supported formats,
+/// an exception is thrown.
+///
+/// @param hex_string Input string.
+/// @param sep character to use as a separator.
+/// @param binary Vector receiving converted string into binary.
+///
+/// @throw isc::BadValue if the format of the input string is invalid.
+void
+decodeSeparatedHexString(const std::string& hex_string,
+ const std::string& sep,
+ std::vector<uint8_t>& binary);
+
+/// @brief Converts a string of hexadecimal digits with colons into
+/// a vector.
+///
+/// Convenience method which calls @c decodeSeparatedHexString() passing
+/// in a colon for the separator.
+
+/// @param hex_string Input string.
+/// @param binary Vector receiving converted string into binary.
+///
+/// @throw isc::BadValue if the format of the input string is invalid.
+void
+decodeColonSeparatedHexString(const std::string& hex_string,
+ std::vector<uint8_t>& binary);
+
+/// @brief Converts a formatted string of hexadecimal digits into
+/// a vector.
+///
+/// This function supports the following formats:
+///
+/// - yy:yy:yy:yy or yy yy yy yy - octets delimited by colons or
+/// spaces, see @c decodeSeparatedHexString
+///
+/// - yyyyyyyyyy
+/// - 0xyyyyyyyyyy
+///
+/// If there is an odd number of hexadecimal digits in the input
+/// string, the '0' is prepended to the string before decoding.
+///
+/// @param hex_string Input string.
+/// @param binary Vector receiving converted string into binary.
+///
+/// @throw isc::BadValue if the format of the input string is invalid.
+void
+decodeFormattedHexString(const std::string& hex_string,
+ std::vector<uint8_t>& binary);
+
+/// @brief Forward declaration to the @c StringSanitizer implementation.
+class StringSanitizerImpl;
+
+/// @brief Type representing the pointer to the @c StringSanitizerImpl.
+typedef boost::shared_ptr<StringSanitizerImpl> StringSanitizerImplPtr;
+
+/// @brief Implements a regular expression based string scrubber
+///
+/// The implementation uses C++11 regex IF the environment supports it
+/// (tested in configure.ac). If not it falls back to C lib regcomp/regexec.
+/// Older compilers, such as pre Gnu g++ 4.9.0, provided only experimental
+/// implementations of regex which are recognized as buggy.
+class StringSanitizer {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Compiles the given character set into a regular expression, and
+ /// retains the given character replacement. Thereafter, the instance
+ /// may be used to scrub an arbitrary number of strings.
+ ///
+ /// @param char_set string containing a regular expression (POSIX
+ /// extended syntax) that describes the characters to replace. If you
+ /// wanted to sanitize hostnames for example, you could specify the
+ /// inversion of valid characters "[^A-Za-z0-9_-]".
+ /// @param char_replacement string of one or more characters to use as the
+ /// replacement for invalid characters.
+ ///
+ /// @throw BadValue if given an invalid regular expression
+ StringSanitizer(const std::string& char_set,
+ const std::string& char_replacement);
+
+ /// @brief Destructor.
+ ///
+ /// Destroys the implementation instance.
+ ~StringSanitizer();
+
+ /// Returns a scrubbed copy of a given string
+ ///
+ /// Replaces all occurrences of characters described by the regular
+ /// expression with the character replacement.
+ ///
+ /// @param original the string to scrub
+ ///
+ /// @throw Unexpected if an error occurs during scrubbing
+ std::string scrub(const std::string& original);
+
+ /// @brief The maximum size for regex parameters.
+ ///
+ /// @note The regex engine is implemented using recursion and can cause
+ /// stack overflow if the input data is too large. An arbitrary size of
+ /// 4096 should be enough for all cases.
+ static const uint32_t MAX_DATA_SIZE;
+
+private:
+ /// @brief Pointer to the @c StringSanitizerImpl.
+ StringSanitizerImplPtr impl_;
+};
+
+/// @brief Type representing the pointer to the @c StringSanitizer.
+typedef boost::shared_ptr<StringSanitizer> StringSanitizerPtr;
+
+/// @brief Check if a string is printable
+///
+/// @param content String to check for printable characters
+///
+/// @return True if empty or contains only printable characters, False otherwise
+inline bool
+isPrintable(const std::string& content) {
+ for (const auto& ch : content) {
+ if (isprint(static_cast<int>(ch)) == 0) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+/// @brief Check if a byte vector is printable
+///
+/// @param content Vector to check for printable characters
+///
+/// @return True if empty or contains only printable characters, False otherwise
+inline bool
+isPrintable(const std::vector<uint8_t>& content) {
+ for (const auto& ch : content) {
+ if (isprint(static_cast<int>(ch)) == 0) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+
+/// @brief Dumps a buffer of bytes as a string of hexadecimal digits
+///
+/// @param data pointer to the data to dump
+/// @param length number of bytes to dump. Caller should ensure the length
+/// does not exceed the buffer.
+std::string dumpAsHex(const uint8_t* data, size_t length);
+
+} // namespace str
+} // namespace util
+} // namespace isc
+
+#endif // STRUTIL_H
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
new file mode 100644
index 0000000..5f8b1e8
--- /dev/null
+++ b/src/lib/util/tests/Makefile.am
@@ -0,0 +1,74 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)\"
+# XXX: we'll pollute the top builddir for creating a temporary test file
+# used to bind a UNIX domain socket so we can minimize the risk of exceeding
+# the limit of file name path size.
+AM_CPPFLAGS += -DTEST_DATA_TOPBUILDDIR=\"$(abs_top_builddir)\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+# CSV files are created by unit tests for CSVFile class.
+CLEANFILES += *.csv
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += bigint_unittest.cc
+run_unittests_SOURCES += base32hex_unittest.cc
+run_unittests_SOURCES += base64_unittest.cc
+run_unittests_SOURCES += boost_time_utils_unittest.cc
+run_unittests_SOURCES += buffer_unittest.cc
+run_unittests_SOURCES += chrono_time_utils_unittest.cc
+run_unittests_SOURCES += csv_file_unittest.cc
+run_unittests_SOURCES += dhcp_space_unittest.cc
+run_unittests_SOURCES += doubles_unittest.cc
+run_unittests_SOURCES += fd_share_tests.cc
+run_unittests_SOURCES += fd_tests.cc
+run_unittests_SOURCES += file_utilities_unittest.cc
+run_unittests_SOURCES += filename_unittest.cc
+run_unittests_SOURCES += hash_unittest.cc
+run_unittests_SOURCES += hex_unittest.cc
+run_unittests_SOURCES += io_utilities_unittest.cc
+run_unittests_SOURCES += labeled_value_unittest.cc
+run_unittests_SOURCES += memory_segment_local_unittest.cc
+run_unittests_SOURCES += memory_segment_common_unittest.h
+run_unittests_SOURCES += memory_segment_common_unittest.cc
+run_unittests_SOURCES += multi_threading_mgr_unittest.cc
+run_unittests_SOURCES += optional_unittest.cc
+run_unittests_SOURCES += pid_file_unittest.cc
+run_unittests_SOURCES += staged_value_unittest.cc
+run_unittests_SOURCES += state_model_unittest.cc
+run_unittests_SOURCES += strutil_unittest.cc
+run_unittests_SOURCES += thread_pool_unittest.cc
+run_unittests_SOURCES += time_utilities_unittest.cc
+run_unittests_SOURCES += triplet_unittest.cc
+run_unittests_SOURCES += range_utilities_unittest.cc
+run_unittests_SOURCES += readwrite_mutex_unittest.cc
+run_unittests_SOURCES += stopwatch_unittest.cc
+run_unittests_SOURCES += unlock_guard_unittests.cc
+run_unittests_SOURCES += utf8_unittest.cc
+run_unittests_SOURCES += versioned_csv_file_unittest.cc
+run_unittests_SOURCES += watch_socket_unittests.cc
+run_unittests_SOURCES += watched_thread_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+run_unittests_LDADD += $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/util/tests/Makefile.in b/src/lib/util/tests/Makefile.in
new file mode 100644
index 0000000..e2f6051
--- /dev/null
+++ b/src/lib/util/tests/Makefile.in
@@ -0,0 +1,1730 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+TESTS = $(am__EXEEXT_1)
+@HAVE_GTEST_TRUE@am__append_1 = run_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/util/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = run_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__run_unittests_SOURCES_DIST = run_unittests.cc bigint_unittest.cc \
+ base32hex_unittest.cc base64_unittest.cc \
+ boost_time_utils_unittest.cc buffer_unittest.cc \
+ chrono_time_utils_unittest.cc csv_file_unittest.cc \
+ dhcp_space_unittest.cc doubles_unittest.cc fd_share_tests.cc \
+ fd_tests.cc file_utilities_unittest.cc filename_unittest.cc \
+ hash_unittest.cc hex_unittest.cc io_utilities_unittest.cc \
+ labeled_value_unittest.cc memory_segment_local_unittest.cc \
+ memory_segment_common_unittest.h \
+ memory_segment_common_unittest.cc \
+ multi_threading_mgr_unittest.cc optional_unittest.cc \
+ pid_file_unittest.cc staged_value_unittest.cc \
+ state_model_unittest.cc strutil_unittest.cc \
+ thread_pool_unittest.cc time_utilities_unittest.cc \
+ triplet_unittest.cc range_utilities_unittest.cc \
+ readwrite_mutex_unittest.cc stopwatch_unittest.cc \
+ unlock_guard_unittests.cc utf8_unittest.cc \
+ versioned_csv_file_unittest.cc watch_socket_unittests.cc \
+ watched_thread_unittest.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-bigint_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-base32hex_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-base64_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-boost_time_utils_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-buffer_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-chrono_time_utils_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-csv_file_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-dhcp_space_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-doubles_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-fd_share_tests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-fd_tests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-file_utilities_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-filename_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-hash_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-hex_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-io_utilities_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-labeled_value_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-memory_segment_local_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-memory_segment_common_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-multi_threading_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-optional_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-pid_file_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-staged_value_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-state_model_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-strutil_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-thread_pool_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-time_utilities_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-triplet_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-range_utilities_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-readwrite_mutex_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-stopwatch_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-unlock_guard_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-utf8_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-versioned_csv_file_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-watch_socket_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-watched_thread_unittest.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/io/libkea-util-io.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+run_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(run_unittests_LDFLAGS) $(LDFLAGS) \
+ -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/run_unittests-base32hex_unittest.Po \
+ ./$(DEPDIR)/run_unittests-base64_unittest.Po \
+ ./$(DEPDIR)/run_unittests-bigint_unittest.Po \
+ ./$(DEPDIR)/run_unittests-boost_time_utils_unittest.Po \
+ ./$(DEPDIR)/run_unittests-buffer_unittest.Po \
+ ./$(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po \
+ ./$(DEPDIR)/run_unittests-csv_file_unittest.Po \
+ ./$(DEPDIR)/run_unittests-dhcp_space_unittest.Po \
+ ./$(DEPDIR)/run_unittests-doubles_unittest.Po \
+ ./$(DEPDIR)/run_unittests-fd_share_tests.Po \
+ ./$(DEPDIR)/run_unittests-fd_tests.Po \
+ ./$(DEPDIR)/run_unittests-file_utilities_unittest.Po \
+ ./$(DEPDIR)/run_unittests-filename_unittest.Po \
+ ./$(DEPDIR)/run_unittests-hash_unittest.Po \
+ ./$(DEPDIR)/run_unittests-hex_unittest.Po \
+ ./$(DEPDIR)/run_unittests-io_utilities_unittest.Po \
+ ./$(DEPDIR)/run_unittests-labeled_value_unittest.Po \
+ ./$(DEPDIR)/run_unittests-memory_segment_common_unittest.Po \
+ ./$(DEPDIR)/run_unittests-memory_segment_local_unittest.Po \
+ ./$(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po \
+ ./$(DEPDIR)/run_unittests-optional_unittest.Po \
+ ./$(DEPDIR)/run_unittests-pid_file_unittest.Po \
+ ./$(DEPDIR)/run_unittests-range_utilities_unittest.Po \
+ ./$(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.Po \
+ ./$(DEPDIR)/run_unittests-staged_value_unittest.Po \
+ ./$(DEPDIR)/run_unittests-state_model_unittest.Po \
+ ./$(DEPDIR)/run_unittests-stopwatch_unittest.Po \
+ ./$(DEPDIR)/run_unittests-strutil_unittest.Po \
+ ./$(DEPDIR)/run_unittests-thread_pool_unittest.Po \
+ ./$(DEPDIR)/run_unittests-time_utilities_unittest.Po \
+ ./$(DEPDIR)/run_unittests-triplet_unittest.Po \
+ ./$(DEPDIR)/run_unittests-unlock_guard_unittests.Po \
+ ./$(DEPDIR)/run_unittests-utf8_unittest.Po \
+ ./$(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po \
+ ./$(DEPDIR)/run_unittests-watch_socket_unittests.Po \
+ ./$(DEPDIR)/run_unittests-watched_thread_unittest.Po
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(run_unittests_SOURCES)
+DIST_SOURCES = $(am__run_unittests_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+# XXX: we'll pollute the top builddir for creating a temporary test file
+# used to bind a UNIX domain socket so we can minimize the risk of exceeding
+# the limit of file name path size.
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) -DTEST_DATA_BUILDDIR=\"$(abs_builddir)\" \
+ -DTEST_DATA_TOPBUILDDIR=\"$(abs_top_builddir)\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+# CSV files are created by unit tests for CSVFile class.
+CLEANFILES = *.gcno *.gcda *.csv
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@run_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ bigint_unittest.cc base32hex_unittest.cc \
+@HAVE_GTEST_TRUE@ base64_unittest.cc \
+@HAVE_GTEST_TRUE@ boost_time_utils_unittest.cc \
+@HAVE_GTEST_TRUE@ buffer_unittest.cc \
+@HAVE_GTEST_TRUE@ chrono_time_utils_unittest.cc \
+@HAVE_GTEST_TRUE@ csv_file_unittest.cc dhcp_space_unittest.cc \
+@HAVE_GTEST_TRUE@ doubles_unittest.cc fd_share_tests.cc \
+@HAVE_GTEST_TRUE@ fd_tests.cc file_utilities_unittest.cc \
+@HAVE_GTEST_TRUE@ filename_unittest.cc hash_unittest.cc \
+@HAVE_GTEST_TRUE@ hex_unittest.cc io_utilities_unittest.cc \
+@HAVE_GTEST_TRUE@ labeled_value_unittest.cc \
+@HAVE_GTEST_TRUE@ memory_segment_local_unittest.cc \
+@HAVE_GTEST_TRUE@ memory_segment_common_unittest.h \
+@HAVE_GTEST_TRUE@ memory_segment_common_unittest.cc \
+@HAVE_GTEST_TRUE@ multi_threading_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@ optional_unittest.cc pid_file_unittest.cc \
+@HAVE_GTEST_TRUE@ staged_value_unittest.cc \
+@HAVE_GTEST_TRUE@ state_model_unittest.cc strutil_unittest.cc \
+@HAVE_GTEST_TRUE@ thread_pool_unittest.cc \
+@HAVE_GTEST_TRUE@ time_utilities_unittest.cc \
+@HAVE_GTEST_TRUE@ triplet_unittest.cc \
+@HAVE_GTEST_TRUE@ range_utilities_unittest.cc \
+@HAVE_GTEST_TRUE@ readwrite_mutex_unittest.cc \
+@HAVE_GTEST_TRUE@ stopwatch_unittest.cc \
+@HAVE_GTEST_TRUE@ unlock_guard_unittests.cc utf8_unittest.cc \
+@HAVE_GTEST_TRUE@ versioned_csv_file_unittest.cc \
+@HAVE_GTEST_TRUE@ watch_socket_unittests.cc \
+@HAVE_GTEST_TRUE@ watched_thread_unittest.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/io/libkea-util-io.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(GTEST_LDADD)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/util/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/tests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+run_unittests$(EXEEXT): $(run_unittests_OBJECTS) $(run_unittests_DEPENDENCIES) $(EXTRA_run_unittests_DEPENDENCIES)
+ @rm -f run_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(run_unittests_LINK) $(run_unittests_OBJECTS) $(run_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-base32hex_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-base64_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-bigint_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-boost_time_utils_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-buffer_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-csv_file_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-dhcp_space_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-doubles_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-fd_share_tests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-fd_tests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-file_utilities_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-filename_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-hash_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-hex_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-io_utilities_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-labeled_value_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-memory_segment_common_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-memory_segment_local_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-optional_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-pid_file_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-range_utilities_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-staged_value_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-state_model_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-stopwatch_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-strutil_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-thread_pool_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-time_utilities_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-triplet_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-unlock_guard_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-utf8_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-watch_socket_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-watched_thread_unittest.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+run_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+run_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+run_unittests-bigint_unittest.o: bigint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-bigint_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-bigint_unittest.Tpo -c -o run_unittests-bigint_unittest.o `test -f 'bigint_unittest.cc' || echo '$(srcdir)/'`bigint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-bigint_unittest.Tpo $(DEPDIR)/run_unittests-bigint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bigint_unittest.cc' object='run_unittests-bigint_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-bigint_unittest.o `test -f 'bigint_unittest.cc' || echo '$(srcdir)/'`bigint_unittest.cc
+
+run_unittests-bigint_unittest.obj: bigint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-bigint_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-bigint_unittest.Tpo -c -o run_unittests-bigint_unittest.obj `if test -f 'bigint_unittest.cc'; then $(CYGPATH_W) 'bigint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/bigint_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-bigint_unittest.Tpo $(DEPDIR)/run_unittests-bigint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bigint_unittest.cc' object='run_unittests-bigint_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-bigint_unittest.obj `if test -f 'bigint_unittest.cc'; then $(CYGPATH_W) 'bigint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/bigint_unittest.cc'; fi`
+
+run_unittests-base32hex_unittest.o: base32hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-base32hex_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-base32hex_unittest.Tpo -c -o run_unittests-base32hex_unittest.o `test -f 'base32hex_unittest.cc' || echo '$(srcdir)/'`base32hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-base32hex_unittest.Tpo $(DEPDIR)/run_unittests-base32hex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='base32hex_unittest.cc' object='run_unittests-base32hex_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-base32hex_unittest.o `test -f 'base32hex_unittest.cc' || echo '$(srcdir)/'`base32hex_unittest.cc
+
+run_unittests-base32hex_unittest.obj: base32hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-base32hex_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-base32hex_unittest.Tpo -c -o run_unittests-base32hex_unittest.obj `if test -f 'base32hex_unittest.cc'; then $(CYGPATH_W) 'base32hex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/base32hex_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-base32hex_unittest.Tpo $(DEPDIR)/run_unittests-base32hex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='base32hex_unittest.cc' object='run_unittests-base32hex_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-base32hex_unittest.obj `if test -f 'base32hex_unittest.cc'; then $(CYGPATH_W) 'base32hex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/base32hex_unittest.cc'; fi`
+
+run_unittests-base64_unittest.o: base64_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-base64_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-base64_unittest.Tpo -c -o run_unittests-base64_unittest.o `test -f 'base64_unittest.cc' || echo '$(srcdir)/'`base64_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-base64_unittest.Tpo $(DEPDIR)/run_unittests-base64_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='base64_unittest.cc' object='run_unittests-base64_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-base64_unittest.o `test -f 'base64_unittest.cc' || echo '$(srcdir)/'`base64_unittest.cc
+
+run_unittests-base64_unittest.obj: base64_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-base64_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-base64_unittest.Tpo -c -o run_unittests-base64_unittest.obj `if test -f 'base64_unittest.cc'; then $(CYGPATH_W) 'base64_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/base64_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-base64_unittest.Tpo $(DEPDIR)/run_unittests-base64_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='base64_unittest.cc' object='run_unittests-base64_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-base64_unittest.obj `if test -f 'base64_unittest.cc'; then $(CYGPATH_W) 'base64_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/base64_unittest.cc'; fi`
+
+run_unittests-boost_time_utils_unittest.o: boost_time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-boost_time_utils_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-boost_time_utils_unittest.Tpo -c -o run_unittests-boost_time_utils_unittest.o `test -f 'boost_time_utils_unittest.cc' || echo '$(srcdir)/'`boost_time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-boost_time_utils_unittest.Tpo $(DEPDIR)/run_unittests-boost_time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='boost_time_utils_unittest.cc' object='run_unittests-boost_time_utils_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-boost_time_utils_unittest.o `test -f 'boost_time_utils_unittest.cc' || echo '$(srcdir)/'`boost_time_utils_unittest.cc
+
+run_unittests-boost_time_utils_unittest.obj: boost_time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-boost_time_utils_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-boost_time_utils_unittest.Tpo -c -o run_unittests-boost_time_utils_unittest.obj `if test -f 'boost_time_utils_unittest.cc'; then $(CYGPATH_W) 'boost_time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/boost_time_utils_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-boost_time_utils_unittest.Tpo $(DEPDIR)/run_unittests-boost_time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='boost_time_utils_unittest.cc' object='run_unittests-boost_time_utils_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-boost_time_utils_unittest.obj `if test -f 'boost_time_utils_unittest.cc'; then $(CYGPATH_W) 'boost_time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/boost_time_utils_unittest.cc'; fi`
+
+run_unittests-buffer_unittest.o: buffer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-buffer_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-buffer_unittest.Tpo -c -o run_unittests-buffer_unittest.o `test -f 'buffer_unittest.cc' || echo '$(srcdir)/'`buffer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-buffer_unittest.Tpo $(DEPDIR)/run_unittests-buffer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='buffer_unittest.cc' object='run_unittests-buffer_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-buffer_unittest.o `test -f 'buffer_unittest.cc' || echo '$(srcdir)/'`buffer_unittest.cc
+
+run_unittests-buffer_unittest.obj: buffer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-buffer_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-buffer_unittest.Tpo -c -o run_unittests-buffer_unittest.obj `if test -f 'buffer_unittest.cc'; then $(CYGPATH_W) 'buffer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/buffer_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-buffer_unittest.Tpo $(DEPDIR)/run_unittests-buffer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='buffer_unittest.cc' object='run_unittests-buffer_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-buffer_unittest.obj `if test -f 'buffer_unittest.cc'; then $(CYGPATH_W) 'buffer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/buffer_unittest.cc'; fi`
+
+run_unittests-chrono_time_utils_unittest.o: chrono_time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-chrono_time_utils_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Tpo -c -o run_unittests-chrono_time_utils_unittest.o `test -f 'chrono_time_utils_unittest.cc' || echo '$(srcdir)/'`chrono_time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Tpo $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='chrono_time_utils_unittest.cc' object='run_unittests-chrono_time_utils_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-chrono_time_utils_unittest.o `test -f 'chrono_time_utils_unittest.cc' || echo '$(srcdir)/'`chrono_time_utils_unittest.cc
+
+run_unittests-chrono_time_utils_unittest.obj: chrono_time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-chrono_time_utils_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Tpo -c -o run_unittests-chrono_time_utils_unittest.obj `if test -f 'chrono_time_utils_unittest.cc'; then $(CYGPATH_W) 'chrono_time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/chrono_time_utils_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Tpo $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='chrono_time_utils_unittest.cc' object='run_unittests-chrono_time_utils_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-chrono_time_utils_unittest.obj `if test -f 'chrono_time_utils_unittest.cc'; then $(CYGPATH_W) 'chrono_time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/chrono_time_utils_unittest.cc'; fi`
+
+run_unittests-csv_file_unittest.o: csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-csv_file_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-csv_file_unittest.Tpo -c -o run_unittests-csv_file_unittest.o `test -f 'csv_file_unittest.cc' || echo '$(srcdir)/'`csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-csv_file_unittest.Tpo $(DEPDIR)/run_unittests-csv_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_file_unittest.cc' object='run_unittests-csv_file_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-csv_file_unittest.o `test -f 'csv_file_unittest.cc' || echo '$(srcdir)/'`csv_file_unittest.cc
+
+run_unittests-csv_file_unittest.obj: csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-csv_file_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-csv_file_unittest.Tpo -c -o run_unittests-csv_file_unittest.obj `if test -f 'csv_file_unittest.cc'; then $(CYGPATH_W) 'csv_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/csv_file_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-csv_file_unittest.Tpo $(DEPDIR)/run_unittests-csv_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_file_unittest.cc' object='run_unittests-csv_file_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-csv_file_unittest.obj `if test -f 'csv_file_unittest.cc'; then $(CYGPATH_W) 'csv_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/csv_file_unittest.cc'; fi`
+
+run_unittests-dhcp_space_unittest.o: dhcp_space_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dhcp_space_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-dhcp_space_unittest.Tpo -c -o run_unittests-dhcp_space_unittest.o `test -f 'dhcp_space_unittest.cc' || echo '$(srcdir)/'`dhcp_space_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dhcp_space_unittest.Tpo $(DEPDIR)/run_unittests-dhcp_space_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_space_unittest.cc' object='run_unittests-dhcp_space_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-dhcp_space_unittest.o `test -f 'dhcp_space_unittest.cc' || echo '$(srcdir)/'`dhcp_space_unittest.cc
+
+run_unittests-dhcp_space_unittest.obj: dhcp_space_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dhcp_space_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-dhcp_space_unittest.Tpo -c -o run_unittests-dhcp_space_unittest.obj `if test -f 'dhcp_space_unittest.cc'; then $(CYGPATH_W) 'dhcp_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp_space_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dhcp_space_unittest.Tpo $(DEPDIR)/run_unittests-dhcp_space_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_space_unittest.cc' object='run_unittests-dhcp_space_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-dhcp_space_unittest.obj `if test -f 'dhcp_space_unittest.cc'; then $(CYGPATH_W) 'dhcp_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp_space_unittest.cc'; fi`
+
+run_unittests-doubles_unittest.o: doubles_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-doubles_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-doubles_unittest.Tpo -c -o run_unittests-doubles_unittest.o `test -f 'doubles_unittest.cc' || echo '$(srcdir)/'`doubles_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-doubles_unittest.Tpo $(DEPDIR)/run_unittests-doubles_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='doubles_unittest.cc' object='run_unittests-doubles_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-doubles_unittest.o `test -f 'doubles_unittest.cc' || echo '$(srcdir)/'`doubles_unittest.cc
+
+run_unittests-doubles_unittest.obj: doubles_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-doubles_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-doubles_unittest.Tpo -c -o run_unittests-doubles_unittest.obj `if test -f 'doubles_unittest.cc'; then $(CYGPATH_W) 'doubles_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/doubles_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-doubles_unittest.Tpo $(DEPDIR)/run_unittests-doubles_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='doubles_unittest.cc' object='run_unittests-doubles_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-doubles_unittest.obj `if test -f 'doubles_unittest.cc'; then $(CYGPATH_W) 'doubles_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/doubles_unittest.cc'; fi`
+
+run_unittests-fd_share_tests.o: fd_share_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-fd_share_tests.o -MD -MP -MF $(DEPDIR)/run_unittests-fd_share_tests.Tpo -c -o run_unittests-fd_share_tests.o `test -f 'fd_share_tests.cc' || echo '$(srcdir)/'`fd_share_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-fd_share_tests.Tpo $(DEPDIR)/run_unittests-fd_share_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fd_share_tests.cc' object='run_unittests-fd_share_tests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-fd_share_tests.o `test -f 'fd_share_tests.cc' || echo '$(srcdir)/'`fd_share_tests.cc
+
+run_unittests-fd_share_tests.obj: fd_share_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-fd_share_tests.obj -MD -MP -MF $(DEPDIR)/run_unittests-fd_share_tests.Tpo -c -o run_unittests-fd_share_tests.obj `if test -f 'fd_share_tests.cc'; then $(CYGPATH_W) 'fd_share_tests.cc'; else $(CYGPATH_W) '$(srcdir)/fd_share_tests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-fd_share_tests.Tpo $(DEPDIR)/run_unittests-fd_share_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fd_share_tests.cc' object='run_unittests-fd_share_tests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-fd_share_tests.obj `if test -f 'fd_share_tests.cc'; then $(CYGPATH_W) 'fd_share_tests.cc'; else $(CYGPATH_W) '$(srcdir)/fd_share_tests.cc'; fi`
+
+run_unittests-fd_tests.o: fd_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-fd_tests.o -MD -MP -MF $(DEPDIR)/run_unittests-fd_tests.Tpo -c -o run_unittests-fd_tests.o `test -f 'fd_tests.cc' || echo '$(srcdir)/'`fd_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-fd_tests.Tpo $(DEPDIR)/run_unittests-fd_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fd_tests.cc' object='run_unittests-fd_tests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-fd_tests.o `test -f 'fd_tests.cc' || echo '$(srcdir)/'`fd_tests.cc
+
+run_unittests-fd_tests.obj: fd_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-fd_tests.obj -MD -MP -MF $(DEPDIR)/run_unittests-fd_tests.Tpo -c -o run_unittests-fd_tests.obj `if test -f 'fd_tests.cc'; then $(CYGPATH_W) 'fd_tests.cc'; else $(CYGPATH_W) '$(srcdir)/fd_tests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-fd_tests.Tpo $(DEPDIR)/run_unittests-fd_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fd_tests.cc' object='run_unittests-fd_tests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-fd_tests.obj `if test -f 'fd_tests.cc'; then $(CYGPATH_W) 'fd_tests.cc'; else $(CYGPATH_W) '$(srcdir)/fd_tests.cc'; fi`
+
+run_unittests-file_utilities_unittest.o: file_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-file_utilities_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-file_utilities_unittest.Tpo -c -o run_unittests-file_utilities_unittest.o `test -f 'file_utilities_unittest.cc' || echo '$(srcdir)/'`file_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-file_utilities_unittest.Tpo $(DEPDIR)/run_unittests-file_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='file_utilities_unittest.cc' object='run_unittests-file_utilities_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-file_utilities_unittest.o `test -f 'file_utilities_unittest.cc' || echo '$(srcdir)/'`file_utilities_unittest.cc
+
+run_unittests-file_utilities_unittest.obj: file_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-file_utilities_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-file_utilities_unittest.Tpo -c -o run_unittests-file_utilities_unittest.obj `if test -f 'file_utilities_unittest.cc'; then $(CYGPATH_W) 'file_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/file_utilities_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-file_utilities_unittest.Tpo $(DEPDIR)/run_unittests-file_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='file_utilities_unittest.cc' object='run_unittests-file_utilities_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-file_utilities_unittest.obj `if test -f 'file_utilities_unittest.cc'; then $(CYGPATH_W) 'file_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/file_utilities_unittest.cc'; fi`
+
+run_unittests-filename_unittest.o: filename_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-filename_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-filename_unittest.Tpo -c -o run_unittests-filename_unittest.o `test -f 'filename_unittest.cc' || echo '$(srcdir)/'`filename_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-filename_unittest.Tpo $(DEPDIR)/run_unittests-filename_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='filename_unittest.cc' object='run_unittests-filename_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-filename_unittest.o `test -f 'filename_unittest.cc' || echo '$(srcdir)/'`filename_unittest.cc
+
+run_unittests-filename_unittest.obj: filename_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-filename_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-filename_unittest.Tpo -c -o run_unittests-filename_unittest.obj `if test -f 'filename_unittest.cc'; then $(CYGPATH_W) 'filename_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/filename_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-filename_unittest.Tpo $(DEPDIR)/run_unittests-filename_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='filename_unittest.cc' object='run_unittests-filename_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-filename_unittest.obj `if test -f 'filename_unittest.cc'; then $(CYGPATH_W) 'filename_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/filename_unittest.cc'; fi`
+
+run_unittests-hash_unittest.o: hash_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hash_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-hash_unittest.Tpo -c -o run_unittests-hash_unittest.o `test -f 'hash_unittest.cc' || echo '$(srcdir)/'`hash_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hash_unittest.Tpo $(DEPDIR)/run_unittests-hash_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hash_unittest.cc' object='run_unittests-hash_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hash_unittest.o `test -f 'hash_unittest.cc' || echo '$(srcdir)/'`hash_unittest.cc
+
+run_unittests-hash_unittest.obj: hash_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hash_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-hash_unittest.Tpo -c -o run_unittests-hash_unittest.obj `if test -f 'hash_unittest.cc'; then $(CYGPATH_W) 'hash_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hash_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hash_unittest.Tpo $(DEPDIR)/run_unittests-hash_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hash_unittest.cc' object='run_unittests-hash_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hash_unittest.obj `if test -f 'hash_unittest.cc'; then $(CYGPATH_W) 'hash_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hash_unittest.cc'; fi`
+
+run_unittests-hex_unittest.o: hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hex_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-hex_unittest.Tpo -c -o run_unittests-hex_unittest.o `test -f 'hex_unittest.cc' || echo '$(srcdir)/'`hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hex_unittest.Tpo $(DEPDIR)/run_unittests-hex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hex_unittest.cc' object='run_unittests-hex_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hex_unittest.o `test -f 'hex_unittest.cc' || echo '$(srcdir)/'`hex_unittest.cc
+
+run_unittests-hex_unittest.obj: hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hex_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-hex_unittest.Tpo -c -o run_unittests-hex_unittest.obj `if test -f 'hex_unittest.cc'; then $(CYGPATH_W) 'hex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hex_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hex_unittest.Tpo $(DEPDIR)/run_unittests-hex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hex_unittest.cc' object='run_unittests-hex_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hex_unittest.obj `if test -f 'hex_unittest.cc'; then $(CYGPATH_W) 'hex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hex_unittest.cc'; fi`
+
+run_unittests-io_utilities_unittest.o: io_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_utilities_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-io_utilities_unittest.Tpo -c -o run_unittests-io_utilities_unittest.o `test -f 'io_utilities_unittest.cc' || echo '$(srcdir)/'`io_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_utilities_unittest.Tpo $(DEPDIR)/run_unittests-io_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_utilities_unittest.cc' object='run_unittests-io_utilities_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_utilities_unittest.o `test -f 'io_utilities_unittest.cc' || echo '$(srcdir)/'`io_utilities_unittest.cc
+
+run_unittests-io_utilities_unittest.obj: io_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_utilities_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-io_utilities_unittest.Tpo -c -o run_unittests-io_utilities_unittest.obj `if test -f 'io_utilities_unittest.cc'; then $(CYGPATH_W) 'io_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_utilities_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_utilities_unittest.Tpo $(DEPDIR)/run_unittests-io_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_utilities_unittest.cc' object='run_unittests-io_utilities_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_utilities_unittest.obj `if test -f 'io_utilities_unittest.cc'; then $(CYGPATH_W) 'io_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_utilities_unittest.cc'; fi`
+
+run_unittests-labeled_value_unittest.o: labeled_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-labeled_value_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-labeled_value_unittest.Tpo -c -o run_unittests-labeled_value_unittest.o `test -f 'labeled_value_unittest.cc' || echo '$(srcdir)/'`labeled_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-labeled_value_unittest.Tpo $(DEPDIR)/run_unittests-labeled_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labeled_value_unittest.cc' object='run_unittests-labeled_value_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-labeled_value_unittest.o `test -f 'labeled_value_unittest.cc' || echo '$(srcdir)/'`labeled_value_unittest.cc
+
+run_unittests-labeled_value_unittest.obj: labeled_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-labeled_value_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-labeled_value_unittest.Tpo -c -o run_unittests-labeled_value_unittest.obj `if test -f 'labeled_value_unittest.cc'; then $(CYGPATH_W) 'labeled_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/labeled_value_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-labeled_value_unittest.Tpo $(DEPDIR)/run_unittests-labeled_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labeled_value_unittest.cc' object='run_unittests-labeled_value_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-labeled_value_unittest.obj `if test -f 'labeled_value_unittest.cc'; then $(CYGPATH_W) 'labeled_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/labeled_value_unittest.cc'; fi`
+
+run_unittests-memory_segment_local_unittest.o: memory_segment_local_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-memory_segment_local_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-memory_segment_local_unittest.Tpo -c -o run_unittests-memory_segment_local_unittest.o `test -f 'memory_segment_local_unittest.cc' || echo '$(srcdir)/'`memory_segment_local_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-memory_segment_local_unittest.Tpo $(DEPDIR)/run_unittests-memory_segment_local_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_segment_local_unittest.cc' object='run_unittests-memory_segment_local_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-memory_segment_local_unittest.o `test -f 'memory_segment_local_unittest.cc' || echo '$(srcdir)/'`memory_segment_local_unittest.cc
+
+run_unittests-memory_segment_local_unittest.obj: memory_segment_local_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-memory_segment_local_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-memory_segment_local_unittest.Tpo -c -o run_unittests-memory_segment_local_unittest.obj `if test -f 'memory_segment_local_unittest.cc'; then $(CYGPATH_W) 'memory_segment_local_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memory_segment_local_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-memory_segment_local_unittest.Tpo $(DEPDIR)/run_unittests-memory_segment_local_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_segment_local_unittest.cc' object='run_unittests-memory_segment_local_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-memory_segment_local_unittest.obj `if test -f 'memory_segment_local_unittest.cc'; then $(CYGPATH_W) 'memory_segment_local_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memory_segment_local_unittest.cc'; fi`
+
+run_unittests-memory_segment_common_unittest.o: memory_segment_common_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-memory_segment_common_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-memory_segment_common_unittest.Tpo -c -o run_unittests-memory_segment_common_unittest.o `test -f 'memory_segment_common_unittest.cc' || echo '$(srcdir)/'`memory_segment_common_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-memory_segment_common_unittest.Tpo $(DEPDIR)/run_unittests-memory_segment_common_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_segment_common_unittest.cc' object='run_unittests-memory_segment_common_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-memory_segment_common_unittest.o `test -f 'memory_segment_common_unittest.cc' || echo '$(srcdir)/'`memory_segment_common_unittest.cc
+
+run_unittests-memory_segment_common_unittest.obj: memory_segment_common_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-memory_segment_common_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-memory_segment_common_unittest.Tpo -c -o run_unittests-memory_segment_common_unittest.obj `if test -f 'memory_segment_common_unittest.cc'; then $(CYGPATH_W) 'memory_segment_common_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memory_segment_common_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-memory_segment_common_unittest.Tpo $(DEPDIR)/run_unittests-memory_segment_common_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_segment_common_unittest.cc' object='run_unittests-memory_segment_common_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-memory_segment_common_unittest.obj `if test -f 'memory_segment_common_unittest.cc'; then $(CYGPATH_W) 'memory_segment_common_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memory_segment_common_unittest.cc'; fi`
+
+run_unittests-multi_threading_mgr_unittest.o: multi_threading_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-multi_threading_mgr_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Tpo -c -o run_unittests-multi_threading_mgr_unittest.o `test -f 'multi_threading_mgr_unittest.cc' || echo '$(srcdir)/'`multi_threading_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Tpo $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='multi_threading_mgr_unittest.cc' object='run_unittests-multi_threading_mgr_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-multi_threading_mgr_unittest.o `test -f 'multi_threading_mgr_unittest.cc' || echo '$(srcdir)/'`multi_threading_mgr_unittest.cc
+
+run_unittests-multi_threading_mgr_unittest.obj: multi_threading_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-multi_threading_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Tpo -c -o run_unittests-multi_threading_mgr_unittest.obj `if test -f 'multi_threading_mgr_unittest.cc'; then $(CYGPATH_W) 'multi_threading_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/multi_threading_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Tpo $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='multi_threading_mgr_unittest.cc' object='run_unittests-multi_threading_mgr_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-multi_threading_mgr_unittest.obj `if test -f 'multi_threading_mgr_unittest.cc'; then $(CYGPATH_W) 'multi_threading_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/multi_threading_mgr_unittest.cc'; fi`
+
+run_unittests-optional_unittest.o: optional_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-optional_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-optional_unittest.Tpo -c -o run_unittests-optional_unittest.o `test -f 'optional_unittest.cc' || echo '$(srcdir)/'`optional_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-optional_unittest.Tpo $(DEPDIR)/run_unittests-optional_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='optional_unittest.cc' object='run_unittests-optional_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-optional_unittest.o `test -f 'optional_unittest.cc' || echo '$(srcdir)/'`optional_unittest.cc
+
+run_unittests-optional_unittest.obj: optional_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-optional_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-optional_unittest.Tpo -c -o run_unittests-optional_unittest.obj `if test -f 'optional_unittest.cc'; then $(CYGPATH_W) 'optional_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/optional_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-optional_unittest.Tpo $(DEPDIR)/run_unittests-optional_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='optional_unittest.cc' object='run_unittests-optional_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-optional_unittest.obj `if test -f 'optional_unittest.cc'; then $(CYGPATH_W) 'optional_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/optional_unittest.cc'; fi`
+
+run_unittests-pid_file_unittest.o: pid_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-pid_file_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-pid_file_unittest.Tpo -c -o run_unittests-pid_file_unittest.o `test -f 'pid_file_unittest.cc' || echo '$(srcdir)/'`pid_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-pid_file_unittest.Tpo $(DEPDIR)/run_unittests-pid_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pid_file_unittest.cc' object='run_unittests-pid_file_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-pid_file_unittest.o `test -f 'pid_file_unittest.cc' || echo '$(srcdir)/'`pid_file_unittest.cc
+
+run_unittests-pid_file_unittest.obj: pid_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-pid_file_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-pid_file_unittest.Tpo -c -o run_unittests-pid_file_unittest.obj `if test -f 'pid_file_unittest.cc'; then $(CYGPATH_W) 'pid_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pid_file_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-pid_file_unittest.Tpo $(DEPDIR)/run_unittests-pid_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pid_file_unittest.cc' object='run_unittests-pid_file_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-pid_file_unittest.obj `if test -f 'pid_file_unittest.cc'; then $(CYGPATH_W) 'pid_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pid_file_unittest.cc'; fi`
+
+run_unittests-staged_value_unittest.o: staged_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-staged_value_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-staged_value_unittest.Tpo -c -o run_unittests-staged_value_unittest.o `test -f 'staged_value_unittest.cc' || echo '$(srcdir)/'`staged_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-staged_value_unittest.Tpo $(DEPDIR)/run_unittests-staged_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='staged_value_unittest.cc' object='run_unittests-staged_value_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-staged_value_unittest.o `test -f 'staged_value_unittest.cc' || echo '$(srcdir)/'`staged_value_unittest.cc
+
+run_unittests-staged_value_unittest.obj: staged_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-staged_value_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-staged_value_unittest.Tpo -c -o run_unittests-staged_value_unittest.obj `if test -f 'staged_value_unittest.cc'; then $(CYGPATH_W) 'staged_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/staged_value_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-staged_value_unittest.Tpo $(DEPDIR)/run_unittests-staged_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='staged_value_unittest.cc' object='run_unittests-staged_value_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-staged_value_unittest.obj `if test -f 'staged_value_unittest.cc'; then $(CYGPATH_W) 'staged_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/staged_value_unittest.cc'; fi`
+
+run_unittests-state_model_unittest.o: state_model_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-state_model_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-state_model_unittest.Tpo -c -o run_unittests-state_model_unittest.o `test -f 'state_model_unittest.cc' || echo '$(srcdir)/'`state_model_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-state_model_unittest.Tpo $(DEPDIR)/run_unittests-state_model_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='state_model_unittest.cc' object='run_unittests-state_model_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-state_model_unittest.o `test -f 'state_model_unittest.cc' || echo '$(srcdir)/'`state_model_unittest.cc
+
+run_unittests-state_model_unittest.obj: state_model_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-state_model_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-state_model_unittest.Tpo -c -o run_unittests-state_model_unittest.obj `if test -f 'state_model_unittest.cc'; then $(CYGPATH_W) 'state_model_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/state_model_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-state_model_unittest.Tpo $(DEPDIR)/run_unittests-state_model_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='state_model_unittest.cc' object='run_unittests-state_model_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-state_model_unittest.obj `if test -f 'state_model_unittest.cc'; then $(CYGPATH_W) 'state_model_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/state_model_unittest.cc'; fi`
+
+run_unittests-strutil_unittest.o: strutil_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-strutil_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-strutil_unittest.Tpo -c -o run_unittests-strutil_unittest.o `test -f 'strutil_unittest.cc' || echo '$(srcdir)/'`strutil_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-strutil_unittest.Tpo $(DEPDIR)/run_unittests-strutil_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='strutil_unittest.cc' object='run_unittests-strutil_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-strutil_unittest.o `test -f 'strutil_unittest.cc' || echo '$(srcdir)/'`strutil_unittest.cc
+
+run_unittests-strutil_unittest.obj: strutil_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-strutil_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-strutil_unittest.Tpo -c -o run_unittests-strutil_unittest.obj `if test -f 'strutil_unittest.cc'; then $(CYGPATH_W) 'strutil_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/strutil_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-strutil_unittest.Tpo $(DEPDIR)/run_unittests-strutil_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='strutil_unittest.cc' object='run_unittests-strutil_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-strutil_unittest.obj `if test -f 'strutil_unittest.cc'; then $(CYGPATH_W) 'strutil_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/strutil_unittest.cc'; fi`
+
+run_unittests-thread_pool_unittest.o: thread_pool_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-thread_pool_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-thread_pool_unittest.Tpo -c -o run_unittests-thread_pool_unittest.o `test -f 'thread_pool_unittest.cc' || echo '$(srcdir)/'`thread_pool_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-thread_pool_unittest.Tpo $(DEPDIR)/run_unittests-thread_pool_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='thread_pool_unittest.cc' object='run_unittests-thread_pool_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-thread_pool_unittest.o `test -f 'thread_pool_unittest.cc' || echo '$(srcdir)/'`thread_pool_unittest.cc
+
+run_unittests-thread_pool_unittest.obj: thread_pool_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-thread_pool_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-thread_pool_unittest.Tpo -c -o run_unittests-thread_pool_unittest.obj `if test -f 'thread_pool_unittest.cc'; then $(CYGPATH_W) 'thread_pool_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/thread_pool_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-thread_pool_unittest.Tpo $(DEPDIR)/run_unittests-thread_pool_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='thread_pool_unittest.cc' object='run_unittests-thread_pool_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-thread_pool_unittest.obj `if test -f 'thread_pool_unittest.cc'; then $(CYGPATH_W) 'thread_pool_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/thread_pool_unittest.cc'; fi`
+
+run_unittests-time_utilities_unittest.o: time_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-time_utilities_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-time_utilities_unittest.Tpo -c -o run_unittests-time_utilities_unittest.o `test -f 'time_utilities_unittest.cc' || echo '$(srcdir)/'`time_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-time_utilities_unittest.Tpo $(DEPDIR)/run_unittests-time_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utilities_unittest.cc' object='run_unittests-time_utilities_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-time_utilities_unittest.o `test -f 'time_utilities_unittest.cc' || echo '$(srcdir)/'`time_utilities_unittest.cc
+
+run_unittests-time_utilities_unittest.obj: time_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-time_utilities_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-time_utilities_unittest.Tpo -c -o run_unittests-time_utilities_unittest.obj `if test -f 'time_utilities_unittest.cc'; then $(CYGPATH_W) 'time_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/time_utilities_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-time_utilities_unittest.Tpo $(DEPDIR)/run_unittests-time_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utilities_unittest.cc' object='run_unittests-time_utilities_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-time_utilities_unittest.obj `if test -f 'time_utilities_unittest.cc'; then $(CYGPATH_W) 'time_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/time_utilities_unittest.cc'; fi`
+
+run_unittests-triplet_unittest.o: triplet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-triplet_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-triplet_unittest.Tpo -c -o run_unittests-triplet_unittest.o `test -f 'triplet_unittest.cc' || echo '$(srcdir)/'`triplet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-triplet_unittest.Tpo $(DEPDIR)/run_unittests-triplet_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='triplet_unittest.cc' object='run_unittests-triplet_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-triplet_unittest.o `test -f 'triplet_unittest.cc' || echo '$(srcdir)/'`triplet_unittest.cc
+
+run_unittests-triplet_unittest.obj: triplet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-triplet_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-triplet_unittest.Tpo -c -o run_unittests-triplet_unittest.obj `if test -f 'triplet_unittest.cc'; then $(CYGPATH_W) 'triplet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/triplet_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-triplet_unittest.Tpo $(DEPDIR)/run_unittests-triplet_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='triplet_unittest.cc' object='run_unittests-triplet_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-triplet_unittest.obj `if test -f 'triplet_unittest.cc'; then $(CYGPATH_W) 'triplet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/triplet_unittest.cc'; fi`
+
+run_unittests-range_utilities_unittest.o: range_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-range_utilities_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-range_utilities_unittest.Tpo -c -o run_unittests-range_utilities_unittest.o `test -f 'range_utilities_unittest.cc' || echo '$(srcdir)/'`range_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-range_utilities_unittest.Tpo $(DEPDIR)/run_unittests-range_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='range_utilities_unittest.cc' object='run_unittests-range_utilities_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-range_utilities_unittest.o `test -f 'range_utilities_unittest.cc' || echo '$(srcdir)/'`range_utilities_unittest.cc
+
+run_unittests-range_utilities_unittest.obj: range_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-range_utilities_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-range_utilities_unittest.Tpo -c -o run_unittests-range_utilities_unittest.obj `if test -f 'range_utilities_unittest.cc'; then $(CYGPATH_W) 'range_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/range_utilities_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-range_utilities_unittest.Tpo $(DEPDIR)/run_unittests-range_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='range_utilities_unittest.cc' object='run_unittests-range_utilities_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-range_utilities_unittest.obj `if test -f 'range_utilities_unittest.cc'; then $(CYGPATH_W) 'range_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/range_utilities_unittest.cc'; fi`
+
+run_unittests-readwrite_mutex_unittest.o: readwrite_mutex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-readwrite_mutex_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Tpo -c -o run_unittests-readwrite_mutex_unittest.o `test -f 'readwrite_mutex_unittest.cc' || echo '$(srcdir)/'`readwrite_mutex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Tpo $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='readwrite_mutex_unittest.cc' object='run_unittests-readwrite_mutex_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-readwrite_mutex_unittest.o `test -f 'readwrite_mutex_unittest.cc' || echo '$(srcdir)/'`readwrite_mutex_unittest.cc
+
+run_unittests-readwrite_mutex_unittest.obj: readwrite_mutex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-readwrite_mutex_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Tpo -c -o run_unittests-readwrite_mutex_unittest.obj `if test -f 'readwrite_mutex_unittest.cc'; then $(CYGPATH_W) 'readwrite_mutex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/readwrite_mutex_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Tpo $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='readwrite_mutex_unittest.cc' object='run_unittests-readwrite_mutex_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-readwrite_mutex_unittest.obj `if test -f 'readwrite_mutex_unittest.cc'; then $(CYGPATH_W) 'readwrite_mutex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/readwrite_mutex_unittest.cc'; fi`
+
+run_unittests-stopwatch_unittest.o: stopwatch_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stopwatch_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-stopwatch_unittest.Tpo -c -o run_unittests-stopwatch_unittest.o `test -f 'stopwatch_unittest.cc' || echo '$(srcdir)/'`stopwatch_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stopwatch_unittest.Tpo $(DEPDIR)/run_unittests-stopwatch_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stopwatch_unittest.cc' object='run_unittests-stopwatch_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-stopwatch_unittest.o `test -f 'stopwatch_unittest.cc' || echo '$(srcdir)/'`stopwatch_unittest.cc
+
+run_unittests-stopwatch_unittest.obj: stopwatch_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stopwatch_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-stopwatch_unittest.Tpo -c -o run_unittests-stopwatch_unittest.obj `if test -f 'stopwatch_unittest.cc'; then $(CYGPATH_W) 'stopwatch_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stopwatch_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stopwatch_unittest.Tpo $(DEPDIR)/run_unittests-stopwatch_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stopwatch_unittest.cc' object='run_unittests-stopwatch_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-stopwatch_unittest.obj `if test -f 'stopwatch_unittest.cc'; then $(CYGPATH_W) 'stopwatch_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stopwatch_unittest.cc'; fi`
+
+run_unittests-unlock_guard_unittests.o: unlock_guard_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unlock_guard_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-unlock_guard_unittests.Tpo -c -o run_unittests-unlock_guard_unittests.o `test -f 'unlock_guard_unittests.cc' || echo '$(srcdir)/'`unlock_guard_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unlock_guard_unittests.Tpo $(DEPDIR)/run_unittests-unlock_guard_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unlock_guard_unittests.cc' object='run_unittests-unlock_guard_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-unlock_guard_unittests.o `test -f 'unlock_guard_unittests.cc' || echo '$(srcdir)/'`unlock_guard_unittests.cc
+
+run_unittests-unlock_guard_unittests.obj: unlock_guard_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unlock_guard_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-unlock_guard_unittests.Tpo -c -o run_unittests-unlock_guard_unittests.obj `if test -f 'unlock_guard_unittests.cc'; then $(CYGPATH_W) 'unlock_guard_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/unlock_guard_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unlock_guard_unittests.Tpo $(DEPDIR)/run_unittests-unlock_guard_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unlock_guard_unittests.cc' object='run_unittests-unlock_guard_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-unlock_guard_unittests.obj `if test -f 'unlock_guard_unittests.cc'; then $(CYGPATH_W) 'unlock_guard_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/unlock_guard_unittests.cc'; fi`
+
+run_unittests-utf8_unittest.o: utf8_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-utf8_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-utf8_unittest.Tpo -c -o run_unittests-utf8_unittest.o `test -f 'utf8_unittest.cc' || echo '$(srcdir)/'`utf8_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-utf8_unittest.Tpo $(DEPDIR)/run_unittests-utf8_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='utf8_unittest.cc' object='run_unittests-utf8_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-utf8_unittest.o `test -f 'utf8_unittest.cc' || echo '$(srcdir)/'`utf8_unittest.cc
+
+run_unittests-utf8_unittest.obj: utf8_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-utf8_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-utf8_unittest.Tpo -c -o run_unittests-utf8_unittest.obj `if test -f 'utf8_unittest.cc'; then $(CYGPATH_W) 'utf8_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/utf8_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-utf8_unittest.Tpo $(DEPDIR)/run_unittests-utf8_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='utf8_unittest.cc' object='run_unittests-utf8_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-utf8_unittest.obj `if test -f 'utf8_unittest.cc'; then $(CYGPATH_W) 'utf8_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/utf8_unittest.cc'; fi`
+
+run_unittests-versioned_csv_file_unittest.o: versioned_csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-versioned_csv_file_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Tpo -c -o run_unittests-versioned_csv_file_unittest.o `test -f 'versioned_csv_file_unittest.cc' || echo '$(srcdir)/'`versioned_csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Tpo $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='versioned_csv_file_unittest.cc' object='run_unittests-versioned_csv_file_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-versioned_csv_file_unittest.o `test -f 'versioned_csv_file_unittest.cc' || echo '$(srcdir)/'`versioned_csv_file_unittest.cc
+
+run_unittests-versioned_csv_file_unittest.obj: versioned_csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-versioned_csv_file_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Tpo -c -o run_unittests-versioned_csv_file_unittest.obj `if test -f 'versioned_csv_file_unittest.cc'; then $(CYGPATH_W) 'versioned_csv_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/versioned_csv_file_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Tpo $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='versioned_csv_file_unittest.cc' object='run_unittests-versioned_csv_file_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-versioned_csv_file_unittest.obj `if test -f 'versioned_csv_file_unittest.cc'; then $(CYGPATH_W) 'versioned_csv_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/versioned_csv_file_unittest.cc'; fi`
+
+run_unittests-watch_socket_unittests.o: watch_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-watch_socket_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-watch_socket_unittests.Tpo -c -o run_unittests-watch_socket_unittests.o `test -f 'watch_socket_unittests.cc' || echo '$(srcdir)/'`watch_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-watch_socket_unittests.Tpo $(DEPDIR)/run_unittests-watch_socket_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='watch_socket_unittests.cc' object='run_unittests-watch_socket_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-watch_socket_unittests.o `test -f 'watch_socket_unittests.cc' || echo '$(srcdir)/'`watch_socket_unittests.cc
+
+run_unittests-watch_socket_unittests.obj: watch_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-watch_socket_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-watch_socket_unittests.Tpo -c -o run_unittests-watch_socket_unittests.obj `if test -f 'watch_socket_unittests.cc'; then $(CYGPATH_W) 'watch_socket_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/watch_socket_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-watch_socket_unittests.Tpo $(DEPDIR)/run_unittests-watch_socket_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='watch_socket_unittests.cc' object='run_unittests-watch_socket_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-watch_socket_unittests.obj `if test -f 'watch_socket_unittests.cc'; then $(CYGPATH_W) 'watch_socket_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/watch_socket_unittests.cc'; fi`
+
+run_unittests-watched_thread_unittest.o: watched_thread_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-watched_thread_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-watched_thread_unittest.Tpo -c -o run_unittests-watched_thread_unittest.o `test -f 'watched_thread_unittest.cc' || echo '$(srcdir)/'`watched_thread_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-watched_thread_unittest.Tpo $(DEPDIR)/run_unittests-watched_thread_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='watched_thread_unittest.cc' object='run_unittests-watched_thread_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-watched_thread_unittest.o `test -f 'watched_thread_unittest.cc' || echo '$(srcdir)/'`watched_thread_unittest.cc
+
+run_unittests-watched_thread_unittest.obj: watched_thread_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-watched_thread_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-watched_thread_unittest.Tpo -c -o run_unittests-watched_thread_unittest.obj `if test -f 'watched_thread_unittest.cc'; then $(CYGPATH_W) 'watched_thread_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/watched_thread_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-watched_thread_unittest.Tpo $(DEPDIR)/run_unittests-watched_thread_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='watched_thread_unittest.cc' object='run_unittests-watched_thread_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-watched_thread_unittest.obj `if test -f 'watched_thread_unittest.cc'; then $(CYGPATH_W) 'watched_thread_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/watched_thread_unittest.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/run_unittests-base32hex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-base64_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-bigint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-boost_time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-buffer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-csv_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-dhcp_space_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-doubles_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-fd_share_tests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-fd_tests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-file_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-filename_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hash_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-labeled_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-memory_segment_common_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-memory_segment_local_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-optional_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-pid_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-range_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-staged_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-state_model_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-stopwatch_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-strutil_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-thread_pool_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-time_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-triplet_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-unlock_guard_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-utf8_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-watch_socket_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-watched_thread_unittest.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/run_unittests-base32hex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-base64_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-bigint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-boost_time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-buffer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-csv_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-dhcp_space_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-doubles_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-fd_share_tests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-fd_tests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-file_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-filename_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hash_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-labeled_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-memory_segment_common_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-memory_segment_local_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-optional_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-pid_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-range_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-staged_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-state_model_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-stopwatch_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-strutil_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-thread_pool_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-time_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-triplet_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-unlock_guard_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-utf8_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-watch_socket_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-watched_thread_unittest.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-TESTS check-am clean clean-generic \
+ clean-libtool clean-noinstPROGRAMS cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/util/tests/base32hex_unittest.cc b/src/lib/util/tests/base32hex_unittest.cc
new file mode 100644
index 0000000..bf28f62
--- /dev/null
+++ b/src/lib/util/tests/base32hex_unittest.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <stdint.h>
+
+#include <cctype>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <util/encode/base32hex.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util::encode;
+
+namespace {
+
+typedef pair<string, string> StringPair;
+
+class Base32HexTest : public ::testing::Test {
+protected:
+ Base32HexTest() : encoding_chars("0123456789ABCDEFGHIJKLMNOPQRSTUV=") {
+ // test vectors from RFC4648
+ test_sequence.push_back(StringPair("", ""));
+ test_sequence.push_back(StringPair("f", "CO======"));
+ test_sequence.push_back(StringPair("fo", "CPNG===="));
+ test_sequence.push_back(StringPair("foo", "CPNMU==="));
+ test_sequence.push_back(StringPair("foob", "CPNMUOG="));
+ test_sequence.push_back(StringPair("fooba", "CPNMUOJ1"));
+ test_sequence.push_back(StringPair("foobar", "CPNMUOJ1E8======"));
+
+ // the same data, encoded using lower case chars (testable only
+ // for the decode side)
+ test_sequence_lower.push_back(StringPair("f", "co======"));
+ test_sequence_lower.push_back(StringPair("fo", "cpng===="));
+ test_sequence_lower.push_back(StringPair("foo", "cpnmu==="));
+ test_sequence_lower.push_back(StringPair("foob", "cpnmuog="));
+ test_sequence_lower.push_back(StringPair("fooba", "cpnmuoj1"));
+ test_sequence_lower.push_back(StringPair("foobar",
+ "cpnmuoj1e8======"));
+ }
+ vector<StringPair> test_sequence;
+ vector<StringPair> test_sequence_lower;
+ vector<uint8_t> decoded_data;
+ const string encoding_chars;
+};
+
+void
+decodeCheck(const string& input_string, vector<uint8_t>& output,
+ const string& expected)
+{
+ decodeBase32Hex(input_string, output);
+ EXPECT_EQ(expected, string(output.begin(), output.end()));
+}
+
+TEST_F(Base32HexTest, decode) {
+ for (vector<StringPair>::const_iterator it = test_sequence.begin();
+ it != test_sequence.end();
+ ++it) {
+ decodeCheck((*it).second, decoded_data, (*it).first);
+ }
+
+ // whitespace should be allowed
+ decodeCheck("CP NM\tUOG=", decoded_data, "foob");
+ decodeCheck("CPNMU===\n", decoded_data, "foo");
+ decodeCheck(" CP NM\tUOG=", decoded_data, "foob");
+ decodeCheck(" ", decoded_data, "");
+
+ // Incomplete input
+ EXPECT_THROW(decodeBase32Hex("CPNMUOJ", decoded_data), BadValue);
+
+ // invalid number of padding characters
+ EXPECT_THROW(decodeBase32Hex("CPNMU0==", decoded_data), BadValue);
+ EXPECT_THROW(decodeBase32Hex("CO0=====", decoded_data), BadValue);
+ EXPECT_THROW(decodeBase32Hex("CO=======", decoded_data), // too many ='s
+ BadValue);
+
+ // intermediate padding isn't allowed
+ EXPECT_THROW(decodeBase32Hex("CPNMUOG=CPNMUOG=", decoded_data), BadValue);
+
+ // Non canonical form isn't allowed.
+ // P => 25(11001), so the padding byte would be 01000000
+ EXPECT_THROW(decodeBase32Hex("0P======", decoded_data), BadValue);
+}
+
+TEST_F(Base32HexTest, decodeLower) {
+ for (vector<StringPair>::const_iterator it = test_sequence_lower.begin();
+ it != test_sequence_lower.end();
+ ++it) {
+ decodeCheck((*it).second, decoded_data, (*it).first);
+ }
+}
+
+TEST_F(Base32HexTest, encode) {
+ for (vector<StringPair>::const_iterator it = test_sequence.begin();
+ it != test_sequence.end();
+ ++it) {
+ decoded_data.assign((*it).first.begin(), (*it).first.end());
+ EXPECT_EQ((*it).second, encodeBase32Hex(decoded_data));
+ }
+}
+
+// For Base32Hex we use handmade mappings, so it's prudent to test the
+// entire mapping table explicitly.
+TEST_F(Base32HexTest, decodeMap) {
+ string input(8, '0'); // input placeholder
+
+ // We're going to populate an input string with only the last character
+ // not equal to the zero character ('0') for each valid base32hex encoding
+ // character. Decoding that input should result in a data stream with
+ // the last byte equal to the numeric value represented by the that
+ // character. For example, we'll generate and confirm the following:
+ // "00000000" => should be 0 (as a 40bit integer)
+ // "00000001" => should be 1 (as a 40bit integer)
+ // ...
+ // "0000000V" => should be 31 (as a 40bit integer)
+ // We also check the use of an invalid character for the last character
+ // surely fails. '=' should be accepted as a valid padding in this
+ // context; space characters shouldn't be allowed in this context.
+
+ for (int i = 0; i < 256; ++i) {
+ input[7] = i;
+
+ const char ch = toupper(i);
+ const size_t pos = encoding_chars.find(ch);
+ if (pos == string::npos) {
+ EXPECT_THROW(decodeBase32Hex(input, decoded_data), BadValue);
+ } else {
+ decodeBase32Hex(input, decoded_data);
+ if (ch == '=') {
+ EXPECT_EQ(4, decoded_data.size());
+ } else {
+ EXPECT_EQ(5, decoded_data.size());
+ EXPECT_EQ(pos, decoded_data[4]);
+ }
+ }
+ }
+}
+
+TEST_F(Base32HexTest, encodeMap) {
+ for (uint8_t i = 0; i < 32; ++i) {
+ decoded_data.assign(4, 0);
+ decoded_data.push_back(i);
+ EXPECT_EQ(encoding_chars[i], encodeBase32Hex(decoded_data)[7]);
+ }
+}
+
+}
diff --git a/src/lib/util/tests/base64_unittest.cc b/src/lib/util/tests/base64_unittest.cc
new file mode 100644
index 0000000..516925e
--- /dev/null
+++ b/src/lib/util/tests/base64_unittest.cc
@@ -0,0 +1,93 @@
+// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <util/encode/base64.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util::encode;
+
+namespace {
+
+typedef pair<string, string> StringPair;
+
+class Base64Test : public ::testing::Test {
+protected:
+ Base64Test()
+ {
+ // test vectors from RFC4648
+ test_sequence.push_back(StringPair("", ""));
+ test_sequence.push_back(StringPair("f", "Zg=="));
+ test_sequence.push_back(StringPair("fo", "Zm8="));
+ test_sequence.push_back(StringPair("foo", "Zm9v"));
+ test_sequence.push_back(StringPair("foob", "Zm9vYg=="));
+ test_sequence.push_back(StringPair("fooba", "Zm9vYmE="));
+ test_sequence.push_back(StringPair("foobar", "Zm9vYmFy"));
+ }
+ vector<StringPair> test_sequence;
+ vector<uint8_t> decoded_data;
+};
+
+void
+decodeCheck(const string& input_string, vector<uint8_t>& output,
+ const string& expected)
+{
+ decodeBase64(input_string, output);
+ EXPECT_EQ(expected, string(output.begin(), output.end()));
+}
+
+TEST_F(Base64Test, decode) {
+ for (vector<StringPair>::const_iterator it = test_sequence.begin();
+ it != test_sequence.end();
+ ++it) {
+ decodeCheck((*it).second, decoded_data, (*it).first);
+ }
+
+ // whitespace should be allowed
+ decodeCheck("Zm 9v\tYmF\ny", decoded_data, "foobar");
+ decodeCheck("Zm9vYg==", decoded_data, "foob");
+ decodeCheck("Zm9vYmE=\n", decoded_data, "fooba");
+ decodeCheck(" Zm9vYmE=\n", decoded_data, "fooba");
+ decodeCheck(" ", decoded_data, "");
+ decodeCheck("\n\t", decoded_data, "");
+
+ // incomplete input
+ EXPECT_THROW(decodeBase64("Zm9vYmF", decoded_data), BadValue);
+
+ // only up to 2 padding characters are allowed
+ EXPECT_THROW(decodeBase64("A===", decoded_data), BadValue);
+ EXPECT_THROW(decodeBase64("A= ==", decoded_data), BadValue);
+
+ // intermediate padding isn't allowed
+ EXPECT_THROW(decodeBase64("YmE=YmE=", decoded_data), BadValue);
+
+ // Non canonical form isn't allowed.
+ // Z => 25(011001), m => 38(100110), 9 => 60(111101), so the padding
+ // byte would be 0100 0000.
+ EXPECT_THROW(decodeBase64("Zm9=", decoded_data), BadValue);
+ // Same for the 1st padding byte. This would make it 01100000.
+ EXPECT_THROW(decodeBase64("Zm==", decoded_data), BadValue);
+}
+
+TEST_F(Base64Test, encode) {
+ for (vector<StringPair>::const_iterator it = test_sequence.begin();
+ it != test_sequence.end();
+ ++it) {
+ decoded_data.assign((*it).first.begin(), (*it).first.end());
+ EXPECT_EQ((*it).second, encodeBase64(decoded_data));
+ }
+}
+}
diff --git a/src/lib/util/tests/bigint_unittest.cc b/src/lib/util/tests/bigint_unittest.cc
new file mode 100644
index 0000000..b811c07
--- /dev/null
+++ b/src/lib/util/tests/bigint_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright (C) 2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <testutils/gtest_utils.h>
+#include <util/bigints.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util;
+
+namespace {
+
+// C++ doesn't allow very big integer literals, so that's why some tests on big
+// numbers might appear like they're unnecessarily circumventing a more obvious
+// test choice.
+
+// Checks that int128_t behaves like a signed integer should.
+TEST(BigintTest, int128) {
+ // Check addition with small numbers.
+ EXPECT_EQ(24, int128_t(16) + int128_t(8));
+
+ // Check subtraction with small numbers.
+ EXPECT_EQ(48, int128_t(64) - int128_t(16));
+
+ // Check multiplication with small numbers.
+ EXPECT_EQ(8, int128_t(2) * int128_t(4));
+
+ // Check division with small numbers.
+ EXPECT_EQ(16, int128_t(64) / int128_t(4));
+
+ // Check rounded division with small numbers.
+ EXPECT_EQ(16, int128_t(65) / int128_t(4));
+
+ // Check that dividing by zero throws.
+ EXPECT_THROW(int128_t(1) / 0, std::overflow_error);
+
+ // Check that underflowing results in a negative number for int128_t.
+ EXPECT_EQ(-1, int128_t(0) - 1);
+
+ // Check that UINT64_MAX < INT128_MAX.
+ EXPECT_LT(uint64_t(0) - 1, int128_t(uint128_t(0) - 1));
+
+ // Check that int128_t is default-initialized to zero. Not a strict
+ // requirement, but something that the current implementation ensures.
+ int128_t i128;
+ EXPECT_EQ(0, i128);
+
+ // Check that overflowing on big numbers has the correct result.
+ i128 = int128_t(0) - 1;
+ EXPECT_EQ(i128 - 1, i128 + i128);
+}
+
+// Checks that uint128_t behaves like an unsigned integer should.
+TEST(BigintTest, uint128) {
+ // Check addition with small numbers.
+ EXPECT_EQ(24, uint128_t(16) + uint128_t(8));
+
+ // Check subtraction with small numbers.
+ EXPECT_EQ(48, uint128_t(64) - uint128_t(16));
+
+ // Check multiplication with small numbers.
+ EXPECT_EQ(8, uint128_t(2) * uint128_t(4));
+
+ // Check division with small numbers.
+ EXPECT_EQ(16, uint128_t(64) / uint128_t(4));
+
+ // Check rounded division with small numbers.
+ EXPECT_EQ(16, uint128_t(65) / uint128_t(4));
+
+ // Check that dividing by zero throws.
+ EXPECT_THROW(uint128_t(1) / 0, std::overflow_error);
+
+ // Check that underflowing results in a positive number for uint128_t.
+ EXPECT_LT(0, uint128_t(0) - 1);
+
+ // Check that UINT64_MAX < UINT128_MAX.
+ EXPECT_LT(uint64_t(0) - 1, uint128_t(0) - 1);
+
+ // Check that uint128_t is default-initialized to zero. Not a strict
+ // requirement, but something that the current implementation ensures.
+ uint128_t u128;
+ EXPECT_EQ(0, u128);
+
+ // Check that overflowing on big numbers has the correct result.
+ u128 = uint128_t(0) - 1;
+ EXPECT_EQ(u128 - 1, u128 + u128);
+}
+
+} // namespace
diff --git a/src/lib/util/tests/boost_time_utils_unittest.cc b/src/lib/util/tests/boost_time_utils_unittest.cc
new file mode 100644
index 0000000..e7e8c81
--- /dev/null
+++ b/src/lib/util/tests/boost_time_utils_unittest.cc
@@ -0,0 +1,99 @@
+// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/boost_time_utils.h>
+
+#include <string.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace boost::posix_time;
+using namespace boost::gregorian;
+
+/// Check the ptimeToText() function returns a numeric month.
+/// Note durationToText() is called by ptimeToText() so is tested too.
+
+// The Posix time epoch is 1970
+TEST(BoostTimeUtilsTest, epoch) {
+ time_t tepoch = 0;
+ ptime pepoch = from_time_t(tepoch);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("1970-01-01 00:00:00");
+ std::string sepoch;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point.
+ expected.push_back('.');
+ }
+
+ if (precision >= 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ sepoch = ptimeToText(pepoch, precision);
+ EXPECT_EQ(expected, sepoch) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ sepoch = ptimeToText(pepoch);
+ EXPECT_EQ(expected, sepoch);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ sepoch = ptimeToText(pepoch, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, sepoch);
+
+}
+
+// The 2015 Bastille day
+TEST(BoostTimeUtilsTest, bastilleDay) {
+ time_duration tdbast =
+ hours(12) + minutes(13) + seconds(14) + milliseconds(500);
+ ptime pbast(date(2015, Jul, 14), tdbast);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("2015-07-14 12:13:14");
+ std::string sbast;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point
+ // and the digit 5 (i.e. 500 ms = .5 secs).
+ expected.push_back('.');
+ expected.push_back('5');
+ } else if (precision > 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ sbast = ptimeToText(pbast, precision);
+ EXPECT_EQ(expected, sbast) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ sbast = ptimeToText(pbast);
+ EXPECT_EQ(expected, sbast);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ sbast = ptimeToText(pbast, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, sbast);
+}
diff --git a/src/lib/util/tests/buffer_unittest.cc b/src/lib/util/tests/buffer_unittest.cc
new file mode 100644
index 0000000..1f63167
--- /dev/null
+++ b/src/lib/util/tests/buffer_unittest.cc
@@ -0,0 +1,341 @@
+// Copyright (C) 2009-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#ifdef EXPECT_DEATH
+#include <util/unittests/resource.h>
+#include <util/unittests/check_valgrind.h>
+#endif /* EXPECT_DEATH */
+
+#include <util/buffer.h>
+
+using namespace isc;
+
+namespace {
+
+using isc::util::InputBuffer;
+using isc::util::OutputBuffer;
+
+class BufferTest : public ::testing::Test {
+protected:
+ BufferTest() : ibuffer(testdata, sizeof(testdata)), obuffer(0),
+ expected_size(0)
+ {
+ data16 = (2 << 8) | 3;
+ data32 = (4 << 24) | (5 << 16) | (6 << 8) | 7;
+ memset(vdata, 0, sizeof(testdata));
+ }
+
+ InputBuffer ibuffer;
+ OutputBuffer obuffer;
+ static const uint8_t testdata[5];
+ uint8_t vdata[sizeof(testdata)];
+ size_t expected_size;
+ uint16_t data16;
+ uint32_t data32;
+};
+
+const uint8_t BufferTest::testdata[5] = {1, 2, 3, 4, 5};
+
+TEST_F(BufferTest, inputBufferRead) {
+ EXPECT_EQ(5, ibuffer.getLength());
+ EXPECT_EQ(1, ibuffer.readUint8());
+ EXPECT_EQ(1, ibuffer.getPosition());
+ data16 = ibuffer.readUint16();
+ EXPECT_EQ((2 << 8) | 3, data16);
+ EXPECT_EQ(3, ibuffer.getPosition());
+ ibuffer.setPosition(1);
+ EXPECT_EQ(1, ibuffer.getPosition());
+ data32 = ibuffer.readUint32();
+ EXPECT_EQ((2 << 24) | (3 << 16) | (4 << 8) | 5, data32);
+ ibuffer.setPosition(0);
+ memset(vdata, 0, sizeof(vdata));
+ ibuffer.readData(vdata, sizeof(vdata));
+ EXPECT_EQ(0, memcmp(vdata, testdata, sizeof(testdata)));
+}
+
+TEST_F(BufferTest, inputBufferException) {
+ EXPECT_THROW(ibuffer.setPosition(6), isc::util::InvalidBufferPosition);
+
+ ibuffer.setPosition(sizeof(testdata));
+ EXPECT_THROW(ibuffer.readUint8(), isc::util::InvalidBufferPosition);
+
+ ibuffer.setPosition(sizeof(testdata) - 1);
+ EXPECT_THROW(ibuffer.readUint16(), isc::util::InvalidBufferPosition);
+
+ ibuffer.setPosition(sizeof(testdata) - 3);
+ EXPECT_THROW(ibuffer.readUint32(), isc::util::InvalidBufferPosition);
+
+ ibuffer.setPosition(sizeof(testdata) - 4);
+ EXPECT_THROW(ibuffer.readData(vdata, sizeof(vdata)),
+ isc::util::InvalidBufferPosition);
+}
+
+TEST_F(BufferTest, outputBufferExtend) {
+ EXPECT_EQ(0, obuffer.getCapacity());
+ EXPECT_EQ(0, obuffer.getLength());
+ obuffer.writeUint8(10);
+ EXPECT_LT(0, obuffer.getCapacity());
+ EXPECT_EQ(1, obuffer.getLength());
+}
+
+TEST_F(BufferTest, outputBufferWrite) {
+ const uint8_t* cp;
+
+ obuffer.writeUint8(1);
+ expected_size += sizeof(uint8_t);
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(1, *cp);
+
+ obuffer.writeUint16(data16);
+ expected_size += sizeof(data16);
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ EXPECT_EQ(2, *(cp + 1));
+ EXPECT_EQ(3, *(cp + 2));
+
+ obuffer.writeUint32(data32);
+ expected_size += sizeof(data32);
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ EXPECT_EQ(4, *(cp + 3));
+ EXPECT_EQ(5, *(cp + 4));
+ EXPECT_EQ(6, *(cp + 5));
+ EXPECT_EQ(7, *(cp + 6));
+
+ obuffer.writeData(testdata, sizeof(testdata));
+ expected_size += sizeof(testdata);
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(0, memcmp(cp + 7, testdata, sizeof(testdata)));
+}
+
+TEST_F(BufferTest, outputBufferWriteat) {
+ obuffer.writeUint32(data32);
+ expected_size += sizeof(data32);
+
+ // overwrite 2nd byte
+ obuffer.writeUint8At(4, 1);
+ EXPECT_EQ(expected_size, obuffer.getLength()); // length shouldn't change
+ const uint8_t* cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(4, *(cp + 1));
+
+ // overwrite 2nd and 3rd bytes
+ obuffer.writeUint16At(data16, 1);
+ EXPECT_EQ(expected_size, obuffer.getLength()); // length shouldn't change
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(2, *(cp + 1));
+ EXPECT_EQ(3, *(cp + 2));
+
+ // overwrite 3rd and 4th bytes
+ obuffer.writeUint16At(data16, 2);
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(2, *(cp + 2));
+ EXPECT_EQ(3, *(cp + 3));
+
+ EXPECT_THROW(obuffer.writeUint8At(data16, 5),
+ isc::util::InvalidBufferPosition);
+ EXPECT_THROW(obuffer.writeUint8At(data16, 4),
+ isc::util::InvalidBufferPosition);
+ EXPECT_THROW(obuffer.writeUint16At(data16, 3),
+ isc::util::InvalidBufferPosition);
+ EXPECT_THROW(obuffer.writeUint16At(data16, 4),
+ isc::util::InvalidBufferPosition);
+ EXPECT_THROW(obuffer.writeUint16At(data16, 5),
+ isc::util::InvalidBufferPosition);
+}
+
+TEST_F(BufferTest, outputBufferSkip) {
+ obuffer.skip(4);
+ EXPECT_EQ(4, obuffer.getLength());
+
+ obuffer.skip(2);
+ EXPECT_EQ(6, obuffer.getLength());
+}
+
+TEST_F(BufferTest, outputBufferTrim) {
+ obuffer.writeData(testdata, sizeof(testdata));
+ EXPECT_EQ(5, obuffer.getLength());
+
+ obuffer.trim(1);
+ EXPECT_EQ(4, obuffer.getLength());
+
+ obuffer.trim(2);
+ EXPECT_EQ(2, obuffer.getLength());
+
+ EXPECT_THROW(obuffer.trim(3), OutOfRange);
+}
+
+TEST_F(BufferTest, outputBufferReadAt) {
+ obuffer.writeData(testdata, sizeof(testdata));
+ for (int i = 0; i < sizeof(testdata); i ++) {
+ EXPECT_EQ(testdata[i], obuffer[i]);
+ }
+ EXPECT_THROW(obuffer[sizeof(testdata)], isc::util::InvalidBufferPosition);
+}
+
+TEST_F(BufferTest, outputBufferClear) {
+ const uint8_t* cp;
+
+ obuffer.writeData(testdata, sizeof(testdata));
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ obuffer.clear();
+ EXPECT_EQ(0, obuffer.getLength());
+ EXPECT_EQ(*cp, 1);
+}
+
+TEST_F(BufferTest, outputBufferWipe) {
+ const uint8_t* cp;
+
+ obuffer.writeData(testdata, sizeof(testdata));
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ obuffer.wipe();
+ EXPECT_EQ(0, obuffer.getLength());
+ EXPECT_EQ(*cp, 0);
+}
+
+TEST_F(BufferTest, emptyOutputBufferWipe) {
+ ASSERT_NO_THROW(obuffer.wipe());
+ EXPECT_EQ(0, obuffer.getLength());
+}
+
+TEST_F(BufferTest, outputBufferCopy) {
+ obuffer.writeData(testdata, sizeof(testdata));
+
+ EXPECT_NO_THROW({
+ OutputBuffer copy(obuffer);
+ ASSERT_EQ(sizeof(testdata), copy.getLength());
+ ASSERT_NE(obuffer.getData(), copy.getData());
+ for (int i = 0; i < sizeof(testdata); i ++) {
+ EXPECT_EQ(testdata[i], copy[i]);
+ if (i + 1 < sizeof(testdata)) {
+ obuffer.writeUint16At(0, i);
+ }
+ EXPECT_EQ(testdata[i], copy[i]);
+ }
+ obuffer.clear();
+ ASSERT_EQ(sizeof(testdata), copy.getLength());
+ });
+}
+
+TEST_F(BufferTest, outputEmptyBufferCopy) {
+ EXPECT_NO_THROW({
+ OutputBuffer copy(obuffer);
+ ASSERT_EQ(0, copy.getLength());
+ });
+}
+
+TEST_F(BufferTest, outputBufferAssign) {
+ OutputBuffer another(0);
+ another.clear();
+ obuffer.writeData(testdata, sizeof(testdata));
+
+ EXPECT_NO_THROW({
+ another = obuffer;
+ ASSERT_EQ(sizeof(testdata), another.getLength());
+ ASSERT_NE(obuffer.getData(), another.getData());
+ for (int i = 0; i < sizeof(testdata); i ++) {
+ EXPECT_EQ(testdata[i], another[i]);
+ if (i + 1 < sizeof(testdata)) {
+ obuffer.writeUint16At(0, i);
+ }
+ EXPECT_EQ(testdata[i], another[i]);
+ }
+ obuffer.clear();
+ ASSERT_EQ(sizeof(testdata), another.getLength());
+ });
+}
+
+TEST_F(BufferTest, outputEmptyBufferAssign) {
+ OutputBuffer copy(0);
+ ASSERT_NO_THROW({
+ copy = obuffer;
+ });
+ ASSERT_EQ(0, copy.getLength());
+ EXPECT_EQ(NULL, copy.getData());
+}
+
+// Check assign to self doesn't break stuff
+TEST_F(BufferTest, outputBufferAssignSelf) {
+ EXPECT_NO_THROW(obuffer = obuffer);
+}
+
+TEST_F(BufferTest, outputBufferZeroSize) {
+ // Some OSes might return NULL on malloc for 0 size, so check it works
+ EXPECT_NO_THROW({
+ OutputBuffer first(0);
+ OutputBuffer copy(first);
+ OutputBuffer second(0);
+ second = first;
+ });
+}
+
+TEST_F(BufferTest, inputBufferReadVectorAll) {
+ std::vector<uint8_t> vec;
+
+ // check that vector can read the whole buffer
+ ibuffer.readVector(vec, 5);
+
+ ASSERT_EQ(5, vec.size());
+ EXPECT_EQ(0, memcmp(&vec[0], testdata, 5));
+
+ // ibuffer is 5 bytes long. Can't read past it.
+ EXPECT_THROW(
+ ibuffer.readVector(vec, 1),
+ isc::util::InvalidBufferPosition
+ );
+}
+
+TEST_F(BufferTest, inputBufferReadVectorChunks) {
+ std::vector<uint8_t> vec;
+
+ // check that vector can read the whole buffer
+ ibuffer.readVector(vec, 3);
+ EXPECT_EQ(3, vec.size());
+
+ EXPECT_EQ(0, memcmp(&vec[0], testdata, 3));
+
+ EXPECT_NO_THROW(
+ ibuffer.readVector(vec, 2)
+ );
+
+ EXPECT_EQ(0, memcmp(&vec[0], testdata+3, 2));
+}
+
+// Tests whether uint64 can be written properly.
+TEST_F(BufferTest, writeUint64) {
+
+ uint64_t val1 = 0x0102030405060708ul;
+ uint64_t val2 = 0xfffffffffffffffful;
+
+ uint8_t exp_val1[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ uint8_t exp_val2[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+ const uint8_t* cp;
+
+ obuffer.writeUint64(val1);
+ ASSERT_EQ(sizeof(uint64_t), obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_TRUE(cp);
+ EXPECT_FALSE(memcmp(exp_val1, obuffer.getData(), sizeof(uint64_t)));
+
+ EXPECT_NO_THROW(obuffer.clear());
+
+ obuffer.writeUint64(val2);
+ ASSERT_EQ(sizeof(uint64_t), obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_TRUE(cp);
+ EXPECT_FALSE(memcmp(exp_val2, obuffer.getData(), sizeof(uint64_t)));
+}
+
+}
diff --git a/src/lib/util/tests/chrono_time_utils_unittest.cc b/src/lib/util/tests/chrono_time_utils_unittest.cc
new file mode 100644
index 0000000..e9d2917
--- /dev/null
+++ b/src/lib/util/tests/chrono_time_utils_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/chrono_time_utils.h>
+
+#include <string.h>
+#include <time.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace std::chrono;
+using namespace isc::util;
+
+/// Check the clockToText() function returns a numeric month.
+TEST(ChronoTimeUtilsTest, epoch) {
+ // The system clock is a wall clock using the local time zone so
+ // the epoch is zero only at some places or of course if the
+ // system is in UTC...
+ struct tm epoch;
+ memset(&epoch, 0, sizeof(epoch));
+ epoch.tm_year = 70;
+ epoch.tm_mday = 1;
+ epoch.tm_isdst = -1;
+ time_t tepoch = mktime(&epoch);
+ system_clock::time_point pepoch = system_clock::from_time_t(tepoch);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("1970-01-01 00:00:00");
+ std::string sepoch;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point.
+ expected.push_back('.');
+ }
+
+ if (precision >= 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ sepoch = clockToText(pepoch, precision);
+ EXPECT_EQ(expected, sepoch) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ sepoch = clockToText(pepoch);
+ EXPECT_EQ(expected, sepoch);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ sepoch = clockToText(pepoch, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, sepoch);
+
+}
+
+/// Check the durationToText() works as expected.
+/// Note durationToText() is not called by clockToText().
+TEST(ChronoTimeUtilsTest, duration) {
+ system_clock::duration p123 = hours(1) + minutes(2) + seconds(3);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("01:02:03");
+ std::string s123;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point.
+ expected.push_back('.');
+ }
+
+ if (precision >= 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ s123 = durationToText(p123, precision);
+ EXPECT_EQ(expected, s123) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ s123 = durationToText(p123);
+ EXPECT_EQ(expected, s123);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ s123 = durationToText(p123, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, s123);
+}
+
+// The 2015 Bastille day
+TEST(ChronoTimeUtilsTest, bastilleDay) {
+ struct tm tm;
+ tm.tm_year = 2015 - 1900;
+ tm.tm_mon = 7 - 1;
+ tm.tm_mday = 14;
+ tm.tm_hour = 12;
+ tm.tm_min = 13;
+ tm.tm_sec = 14;
+ tm.tm_isdst = -1;
+ time_t tbast = mktime(&tm);
+ system_clock::time_point tpbast = system_clock::from_time_t(tbast);
+ tpbast += milliseconds(500);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("2015-07-14 12:13:14");
+ std::string sbast;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point
+ // and the digit 5 (i.e. 500 ms = .5 secs).
+ expected.push_back('.');
+ expected.push_back('5');
+ } else if (precision > 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ sbast = clockToText(tpbast, precision);
+ EXPECT_EQ(expected, sbast) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ sbast = clockToText(tpbast);
+ EXPECT_EQ(expected, sbast);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ sbast = clockToText(tpbast, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, sbast);
+}
+
+// Try steady clock duration.
+TEST(ChronoTimeUtilsTest, steadyClock) {
+ steady_clock::duration p12345 = hours(1) + minutes(2) + seconds(3) +
+ milliseconds(4) + microseconds(5);
+ std::string expected("01:02:03.004005");
+ std::string s12345 = durationToText(p12345, 6);
+ EXPECT_EQ(expected, s12345);
+}
diff --git a/src/lib/util/tests/csv_file_unittest.cc b/src/lib/util/tests/csv_file_unittest.cc
new file mode 100644
index 0000000..f55ec55
--- /dev/null
+++ b/src/lib/util/tests/csv_file_unittest.cc
@@ -0,0 +1,700 @@
+// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <util/csv_file.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+namespace {
+
+using namespace isc::util;
+
+// This test exercises escaping and unescaping of characters.
+TEST(CSVRowTest, escapeUnescape) {
+ std::string orig(",FO^O\\,B?,AR,");
+
+ // We'll escape commas, question marks, and carets.
+ std::string escaped = CSVRow::escapeCharacters(orig, ",?^");
+ EXPECT_EQ ("&#x2cFO&#x5eO\\&#x2cB&#x3f&#x2cAR&#x2c", escaped);
+
+ // Now make sure we can unescape it correctly.
+ std::string unescaped = CSVRow::unescapeCharacters(escaped);
+ EXPECT_EQ (orig, unescaped);
+
+ // Make sure that an incident occurrence of just the escape tag
+ // is left intact.
+ orig = ("no&#xescape");
+ escaped = CSVRow::escapeCharacters(orig, ",");
+ unescaped = CSVRow::unescapeCharacters(orig);
+ EXPECT_EQ (orig, unescaped);
+
+ // Make sure that an incidental occurrence of a valid
+ // escape tag sequence left intact.
+ orig = ("no&#x2cescape");
+ escaped = CSVRow::escapeCharacters(orig, ",");
+ unescaped = CSVRow::unescapeCharacters(escaped);
+ EXPECT_EQ (orig, unescaped);
+}
+
+// This test checks that the single data row is parsed.
+TEST(CSVRow, parse) {
+ CSVRow row0("foo,bar,foo-bar");
+ ASSERT_EQ(3, row0.getValuesCount());
+ EXPECT_EQ("foo", row0.readAt(0));
+ EXPECT_EQ("bar", row0.readAt(1));
+ EXPECT_EQ("foo-bar", row0.readAt(2));
+
+ row0.parse("bar,,foo-bar");
+ ASSERT_EQ(3, row0.getValuesCount());
+ EXPECT_EQ("bar", row0.readAt(0));
+ EXPECT_TRUE(row0.readAt(1).empty());
+ EXPECT_EQ("foo-bar", row0.readAt(2));
+
+ row0.parse("bar,foo&#x2c-bar");
+ ASSERT_EQ(2, row0.getValuesCount());
+ EXPECT_EQ("bar", row0.readAt(0));
+ // Read the second column as-is and escaped
+ EXPECT_EQ("foo&#x2c-bar", row0.readAt(1));
+ EXPECT_EQ("foo,-bar", row0.readAtEscaped(1));
+
+ CSVRow row1("foo-bar|foo|bar|", '|');
+ ASSERT_EQ(4, row1.getValuesCount());
+ EXPECT_EQ("foo-bar", row1.readAt(0));
+ EXPECT_EQ("foo", row1.readAt(1));
+ EXPECT_EQ("bar", row1.readAt(2));
+ EXPECT_TRUE(row1.readAt(3).empty());
+
+ row1.parse("");
+ ASSERT_EQ(1, row1.getValuesCount());
+ EXPECT_TRUE(row1.readAt(0).empty());
+}
+
+// Verifies that empty columns are handled correctly.
+TEST(CSVRow, emptyColumns) {
+ // Should get four columns, all blank except column the second one.
+ CSVRow row(",one,,");
+ ASSERT_EQ(4, row.getValuesCount());
+ EXPECT_EQ("", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+ EXPECT_EQ("", row.readAt(2));
+ EXPECT_EQ("", row.readAt(3));
+}
+
+// Verifies that empty columns are handled correctly.
+TEST(CSVRow, oneColumn) {
+ // Should get one column
+ CSVRow row("zero");
+ ASSERT_EQ(1, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+}
+
+// This test checks that the text representation of the CSV row
+// is created correctly.
+TEST(CSVRow, render) {
+ CSVRow row0(3);
+ row0.writeAt(0, "foo");
+ row0.writeAt(1, "foo-bar");
+ row0.writeAt(2, "bar");
+
+ std::string text;
+ ASSERT_NO_THROW(text = row0.render());
+ EXPECT_EQ(text, "foo,foo-bar,bar");
+
+ CSVRow row1(4, ';');
+ row1.writeAt(0, "foo");
+ row1.writeAt(2, "bar");
+ row1.writeAt(3, 10);
+
+ ASSERT_NO_THROW(text = row1.render());
+ EXPECT_EQ(text, "foo;;bar;10");
+
+ CSVRow row2(0);
+ ASSERT_NO_THROW(text = row2.render());
+ EXPECT_TRUE(text.empty());
+}
+
+// This test checks that the data values can be set for the CSV row.
+TEST(CSVRow, writeAt) {
+ CSVRow row(4);
+ row.writeAt(0, 10);
+ row.writeAt(1, "foo");
+ row.writeAt(2, "bar");
+ row.writeAtEscaped(3, "bar,one,two");
+
+ EXPECT_EQ("10", row.readAt(0));
+ EXPECT_EQ("foo", row.readAt(1));
+ EXPECT_EQ("bar", row.readAt(2));
+ // Read third column as-is and unescaped
+ EXPECT_EQ("bar&#x2cone&#x2ctwo", row.readAt(3));
+ EXPECT_EQ("bar,one,two", row.readAtEscaped(3));
+
+ EXPECT_THROW(row.writeAt(4, 20), CSVFileError);
+ EXPECT_THROW(row.writeAt(4, "foo"), CSVFileError);
+}
+
+// Checks whether writeAt() and append() can be mixed together.
+TEST(CSVRow, append) {
+ CSVRow row(3);
+
+ EXPECT_EQ(3, row.getValuesCount());
+
+ row.writeAt(0, "alpha");
+ ASSERT_NO_THROW(row.append("delta"));
+ EXPECT_EQ(4, row.getValuesCount());
+ row.writeAt(1, "beta");
+ row.writeAt(2, "gamma");
+ ASSERT_NO_THROW(row.append("epsilon"));
+ EXPECT_EQ(5, row.getValuesCount());
+
+ std::string text;
+ ASSERT_NO_THROW(text = row.render());
+ EXPECT_EQ("alpha,beta,gamma,delta,epsilon", text);
+}
+
+// This test checks that a row can be trimmed of
+// a given number of elements
+TEST(CSVRow, trim) {
+ CSVRow row("zero,one,two,three,four");
+ ASSERT_EQ(5, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+ EXPECT_EQ("two", row.readAt(2));
+ EXPECT_EQ("three", row.readAt(3));
+ EXPECT_EQ("four", row.readAt(4));
+
+ ASSERT_THROW(row.trim(10), CSVFileError);
+
+ // Verify that we can erase just one
+ ASSERT_NO_THROW(row.trim(1));
+ ASSERT_EQ(4, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+ EXPECT_EQ("two", row.readAt(2));
+ EXPECT_EQ("three", row.readAt(3));
+
+ // Verify we can trim more than one
+ ASSERT_NO_THROW(row.trim(2));
+ ASSERT_EQ(2, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+}
+
+/// @brief Test fixture class for testing operations on CSV file.
+///
+/// It implements basic operations on files, such as reading writing
+/// file removal and checking presence of the file. This is used by
+/// unit tests to verify correctness of the file created by the
+/// CSVFile class.
+class CSVFileTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets the path to the CSV file used throughout the tests.
+ /// The name of the file is test.csv and it is located in the
+ /// current build folder.
+ ///
+ /// It also deletes any dangling files after previous tests.
+ CSVFileTest();
+
+ /// @brief Destructor.
+ ///
+ /// Deletes the test CSV file if any.
+ virtual ~CSVFileTest();
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Check if test file exists on disk.
+ bool exists() const;
+
+ /// @brief Reads whole CSV file.
+ ///
+ /// @return Contents of the file.
+ std::string readFile() const;
+
+ /// @brief Removes existing file (if any).
+ int removeFile() const;
+
+ /// @brief Creates file with contents.
+ ///
+ /// @param contents Contents of the file.
+ void writeFile(const std::string& contents) const;
+
+ /// @brief Absolute path to the file used in the tests.
+ std::string testfile_;
+
+};
+
+CSVFileTest::CSVFileTest()
+ : testfile_(absolutePath("test.csv")) {
+ static_cast<void>(removeFile());
+}
+
+CSVFileTest::~CSVFileTest() {
+ static_cast<void>(removeFile());
+}
+
+std::string
+CSVFileTest::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << TEST_DATA_BUILDDIR << "/" << filename;
+ return (s.str());
+}
+
+bool
+CSVFileTest::exists() const {
+ std::ifstream fs(testfile_.c_str());
+ bool ok = fs.good();
+ fs.close();
+ return (ok);
+}
+
+std::string
+CSVFileTest::readFile() const {
+ std::ifstream fs(testfile_.c_str());
+ if (!fs.is_open()) {
+ return ("");
+ }
+ std::string contents((std::istreambuf_iterator<char>(fs)),
+ std::istreambuf_iterator<char>());
+ fs.close();
+ return (contents);
+}
+
+int
+CSVFileTest::removeFile() const {
+ return (remove(testfile_.c_str()));
+}
+
+void
+CSVFileTest::writeFile(const std::string& contents) const {
+ std::ofstream fs(testfile_.c_str(), std::ofstream::out);
+ if (fs.is_open()) {
+ fs << contents;
+ fs.close();
+ }
+}
+
+// This test checks that the function which is used to add columns of the
+// CSV file works as expected.
+TEST_F(CSVFileTest, addColumn) {
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ // Add two columns.
+ ASSERT_NO_THROW(csv->addColumn("animal"));
+ ASSERT_NO_THROW(csv->addColumn("color"));
+ // Make sure we can't add duplicates.
+ EXPECT_THROW(csv->addColumn("animal"), CSVFileError);
+ EXPECT_THROW(csv->addColumn("color"), CSVFileError);
+ // But we should still be able to add unique columns.
+ EXPECT_NO_THROW(csv->addColumn("age"));
+ EXPECT_NO_THROW(csv->addColumn("comments"));
+ // Assert that the file is opened, because the rest of the test relies
+ // on this.
+ ASSERT_NO_THROW(csv->recreate());
+ ASSERT_TRUE(exists());
+
+ // Make sure we can't add columns (even unique) when the file is open.
+ ASSERT_THROW(csv->addColumn("zoo"), CSVFileError);
+ // Close the file.
+ ASSERT_NO_THROW(csv->close());
+ // And check that now it is possible to add the column.
+ EXPECT_NO_THROW(csv->addColumn("zoo"));
+}
+
+// This test checks that the appropriate file name is initialized.
+TEST_F(CSVFileTest, getFilename) {
+ CSVFile csv(testfile_);
+ EXPECT_EQ(testfile_, csv.getFilename());
+}
+
+// This test checks that the file can be opened, its whole content is
+// parsed correctly and data may be appended. It also checks that empty
+// row is returned when EOF is reached.
+TEST_F(CSVFileTest, openReadAllWrite) {
+ // Create a new CSV file that contains a header and two data rows.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n");
+
+ // Open this file and check that the header is parsed.
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ ASSERT_NO_THROW(csv->open());
+ ASSERT_EQ(3, csv->getColumnCount());
+ EXPECT_EQ("animal", csv->getColumnName(0));
+ EXPECT_EQ("age", csv->getColumnName(1));
+ EXPECT_EQ("color", csv->getColumnName(2));
+
+ // Read first row.
+ CSVRow row;
+ ASSERT_TRUE(csv->next(row));
+ ASSERT_EQ(3, row.getValuesCount());
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("10", row.readAt(1));
+ EXPECT_EQ("white", row.readAt(2));
+
+ // Read second row.
+ ASSERT_TRUE(csv->next(row));
+ ASSERT_EQ(3, row.getValuesCount());
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("15", row.readAt(1));
+ EXPECT_EQ("yellow", row.readAt(2));
+
+ // There is no 3rd row, so the empty one should be returned.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // It should be fine to read again, but again empty row should be returned.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Now, let's try to append something to this file.
+ CSVRow row_write(3);
+ row_write.writeAt(0, "dog");
+ row_write.writeAt(1, 2);
+ row_write.writeAt(2, "blue");
+ ASSERT_NO_THROW(csv->append(row_write));
+
+ // Close the file.
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+
+ // Check the file contents are correct.
+ EXPECT_EQ("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n"
+ "dog,2,blue\n",
+ readFile());
+
+ // Any attempt to read from the file or write to it should now fail.
+ EXPECT_FALSE(csv->next(row));
+ EXPECT_THROW(csv->append(row_write), CSVFileError);
+
+ CSVRow row_write2(3);
+ row_write2.writeAt(0, "bird");
+ row_write2.writeAt(1, 3);
+ row_write2.writeAt(2, "purple");
+
+ // Reopen the file, seek to the end of file so as we can append
+ // some more data.
+ ASSERT_NO_THROW(csv->open(true));
+ // The file pointer should be at the end of file, so an attempt
+ // to read should result in an empty row.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+ // We should be able to append new data.
+ ASSERT_NO_THROW(csv->append(row_write2));
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+ // Check that new data has been appended.
+ EXPECT_EQ("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n"
+ "dog,2,blue\n"
+ "bird,3,purple\n",
+ readFile());
+}
+
+// This test checks that contents may be appended to a file which hasn't
+// been fully parsed/read.
+TEST_F(CSVFileTest, openReadPartialWrite) {
+ // Create a CSV file with two rows in it.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n");
+
+ // Open this file.
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ ASSERT_NO_THROW(csv->open());
+
+ // Read the first row.
+ CSVRow row0(0);
+ ASSERT_NO_THROW(csv->next(row0));
+ ASSERT_EQ(3, row0.getValuesCount());
+ EXPECT_EQ("cat", row0.readAt(0));
+ EXPECT_EQ("10", row0.readAt(1));
+ EXPECT_EQ("white", row0.readAt(2));
+
+ // There is still second row to be read. But, it should be possible to
+ // skip reading it and append new row to the end of file.
+ CSVRow row_write(3);
+ row_write.writeAt(0, "dog");
+ row_write.writeAt(1, 2);
+ row_write.writeAt(2, "blue");
+ ASSERT_NO_THROW(csv->append(row_write));
+
+ // At this point, the file pointer is at the end of file, so reading
+ // should return empty row.
+ CSVRow row1(0);
+ ASSERT_NO_THROW(csv->next(row1));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row1);
+
+ // Close the file.
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+
+ // Check that there are two initial lines and one new there.
+ EXPECT_EQ("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n"
+ "dog,2,blue\n",
+ readFile());
+
+}
+
+// This test checks that the new CSV file is created and header
+// is written to it. It also checks that data rows can be
+// appended to it.
+TEST_F(CSVFileTest, recreate) {
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ csv->addColumn("animal");
+ csv->addColumn("color");
+ csv->addColumn("age");
+ csv->addColumn("comments");
+ ASSERT_NO_THROW(csv->recreate());
+ ASSERT_TRUE(exists());
+
+ CSVRow row0(4);
+ row0.writeAt(0, "dog");
+ row0.writeAt(1, "grey");
+ row0.writeAt(2, 3);
+ row0.writeAt(3, "nice one");
+ ASSERT_NO_THROW(csv->append(row0));
+
+ CSVRow row1(4);
+ row1.writeAt(0, "cat");
+ row1.writeAt(1, "black");
+ row1.writeAt(2, 2);
+ ASSERT_NO_THROW(csv->append(row1));
+
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+
+ EXPECT_EQ("animal,color,age,comments\n"
+ "dog,grey,3,nice one\n"
+ "cat,black,2,\n",
+ readFile());
+}
+
+// This test checks that the error is reported when the size of the row being
+// read doesn't match the number of columns of the CSV file.
+TEST_F(CSVFileTest, validate) {
+ // Create CSV file with 2 invalid rows in it: one too long, one too short.
+ // Apart from that, there are two valid columns that should be read
+ // successfully.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow,black\n"
+ "dog,3,green\n"
+ "elephant,11\n");
+
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ ASSERT_NO_THROW(csv->open());
+ // First row is correct.
+ CSVRow row0;
+ ASSERT_TRUE(csv->next(row0));
+ EXPECT_EQ("cat", row0.readAt(0));
+ EXPECT_EQ("10", row0.readAt(1));
+ EXPECT_EQ("white", row0.readAt(2));
+ EXPECT_EQ("success", csv->getReadMsg());
+ // This row is too long.
+ CSVRow row1;
+ EXPECT_FALSE(csv->next(row1));
+ EXPECT_NE("success", csv->getReadMsg());
+ // This row is correct.
+ CSVRow row2;
+ ASSERT_TRUE(csv->next(row2));
+ EXPECT_EQ("dog", row2.readAt(0));
+ EXPECT_EQ("3", row2.readAt(1));
+ EXPECT_EQ("green", row2.readAt(2));
+ EXPECT_EQ("success", csv->getReadMsg());
+ // This row is too short.
+ CSVRow row3;
+ EXPECT_FALSE(csv->next(row3));
+ EXPECT_NE("success", csv->getReadMsg());
+}
+
+// Test test checks that exception is thrown when the header of the CSV file
+// parsed, doesn't match the columns specified.
+TEST_F(CSVFileTest, validateHeader) {
+ // Create CSV file with 3 columns.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow,black\n");
+
+ // Invalid order of columns.
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ csv->addColumn("color");
+ csv->addColumn("animal");
+ csv->addColumn("age");
+ EXPECT_THROW(csv->open(), CSVFileError);
+
+ // Too many columns.
+ csv.reset(new CSVFile(testfile_));
+ csv->addColumn("animal");
+ csv->addColumn("age");
+ csv->addColumn("color");
+ csv->addColumn("notes");
+ EXPECT_THROW(csv->open(), CSVFileError);
+
+ // Too few columns.
+ csv.reset(new CSVFile(testfile_));
+ csv->addColumn("animal");
+ csv->addColumn("age");
+ EXPECT_THROW(csv->open(), CSVFileError);
+}
+
+// This test checks that the exists method of the CSVFile class properly
+// checks that the file exists.
+TEST_F(CSVFileTest, exists) {
+ // Create a new CSV file that contains a header and two data rows.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n");
+
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ // The CSVFile class should return true even if the file hasn't been
+ // opened.
+ EXPECT_TRUE(csv->exists());
+ // Now open the file and make sure it still returns true.
+ ASSERT_NO_THROW(csv->open());
+ EXPECT_TRUE(csv->exists());
+
+ // Close the file and remove it.
+ csv->close();
+ EXPECT_EQ(0, removeFile());
+
+ // The file should not exist.
+ EXPECT_FALSE(csv->exists());
+}
+
+// Check that a single header without a trailing blank line can be parsed.
+TEST_F(CSVFileTest, parseHeaderWithoutTrailingBlankLine) {
+ // Create a new CSV file that only contains a header without a new line.
+ writeFile("animal,age,color");
+
+ // Open this file and check that the header is parsed.
+ CSVFile csv(testfile_);
+ ASSERT_NO_THROW(csv.open());
+ ASSERT_EQ(3, csv.getColumnCount());
+ EXPECT_EQ("animal", csv.getColumnName(0));
+ EXPECT_EQ("age", csv.getColumnName(1));
+ EXPECT_EQ("color", csv.getColumnName(2));
+
+ // Attempt to read the next row which doesn't exist.
+ CSVRow row;
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Close the file.
+ csv.close();
+}
+
+// Check that content without a trailing blank line can be parsed.
+TEST_F(CSVFileTest, parseContentWithoutTrailingBlankLine) {
+ // Now create a new CSV file that contains header plus data, but the last
+ // line is missing a new line.
+ writeFile("animal,age,color\n"
+ "cat,4,white\n"
+ "lion,8,yellow");
+
+ // Open this file and check that the header is parsed.
+ CSVFile csv(testfile_);
+ ASSERT_NO_THROW(csv.open());
+ ASSERT_EQ(3, csv.getColumnCount());
+ EXPECT_EQ("animal", csv.getColumnName(0));
+ EXPECT_EQ("age", csv.getColumnName(1));
+ EXPECT_EQ("color", csv.getColumnName(2));
+
+ // Check the first data row.
+ CSVRow row;
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("4", row.readAt(1));
+ EXPECT_EQ("white", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Check the second data row.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("8", row.readAt(1));
+ EXPECT_EQ("yellow", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Attempt to read the next row which doesn't exist.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Close the file.
+ csv.close();
+}
+
+// Check that blank lines are skipped when reading from a file.
+TEST_F(CSVFileTest, parseContentWithBlankLines) {
+ for (char const* const& content : {
+ // Single intermediary blank line
+ "animal,age,color\n"
+ "cat,4,white\n"
+ "\n"
+ "lion,8,yellow\n",
+
+ // Blank lines all over
+ "\n"
+ "\n"
+ "animal,age,color\n"
+ "\n"
+ "\n"
+ "cat,4,white\n"
+ "\n"
+ "\n"
+ "lion,8,yellow\n"
+ "\n"
+ "\n",
+ }) {
+ // Create a new CSV file.
+ writeFile(content);
+
+ // Open this file and check that the header is parsed.
+ CSVFile csv(testfile_);
+ ASSERT_NO_THROW(csv.open());
+ ASSERT_EQ(3, csv.getColumnCount());
+ EXPECT_EQ("animal", csv.getColumnName(0));
+ EXPECT_EQ("age", csv.getColumnName(1));
+ EXPECT_EQ("color", csv.getColumnName(2));
+
+ // Check the first data row.
+ CSVRow row;
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("4", row.readAt(1));
+ EXPECT_EQ("white", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Check the second non-blank data row.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("8", row.readAt(1));
+ EXPECT_EQ("yellow", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Attempt to read the next row which doesn't exist.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Close the file.
+ csv.close();
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/dhcp_space_unittest.cc b/src/lib/util/tests/dhcp_space_unittest.cc
new file mode 100644
index 0000000..e5269c4
--- /dev/null
+++ b/src/lib/util/tests/dhcp_space_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/dhcp_space.h>
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc::util;
+
+TEST(DhcpSpace, cString) {
+ EXPECT_EQ(std::strcmp(cStringDhcpSpace<DHCPv4>(), "4"), 0);
+ EXPECT_EQ(std::strcmp(cStringDhcpSpace<DHCPv6>(), "6"), 0);
+}
+
+TEST(DhcpSpace, format) {
+ EXPECT_EQ(formatDhcpSpace<DHCPv4>("dhcp{}"), "dhcp4");
+ EXPECT_EQ(formatDhcpSpace<DHCPv6>("dhcp{}"), "dhcp6");
+
+ EXPECT_EQ(formatDhcpSpace<DHCPv4>("Dhcp{}.subnet{}"), "Dhcp4.subnet4");
+ EXPECT_EQ(formatDhcpSpace<DHCPv6>("Dhcp{}.subnet{}"), "Dhcp6.subnet6");
+}
+
+} // namespace
diff --git a/src/lib/util/tests/doubles_unittest.cc b/src/lib/util/tests/doubles_unittest.cc
new file mode 100644
index 0000000..1c17f90
--- /dev/null
+++ b/src/lib/util/tests/doubles_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright (C) 2019,2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/doubles.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+// Exercises isc::util::areDoublesEquivalent().
+TEST(Doubles, areDoublesEquivalent) {
+ std::vector<uint8_t> data;
+
+ // Default tolerance is 0.000001
+ EXPECT_TRUE(areDoublesEquivalent( 1.0000000, 1.0000005));
+ EXPECT_FALSE(areDoublesEquivalent(1.0000000, 1.000005));
+
+ // Check custom tolerance.
+ EXPECT_TRUE(areDoublesEquivalent( 1.000, 1.005, 0.01));
+ EXPECT_FALSE(areDoublesEquivalent(1.000, 1.005, 0.001));
+}
+
+}
diff --git a/src/lib/util/tests/fd_share_tests.cc b/src/lib/util/tests/fd_share_tests.cc
new file mode 100644
index 0000000..f870ec2
--- /dev/null
+++ b/src/lib/util/tests/fd_share_tests.cc
@@ -0,0 +1,72 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/io/fd.h>
+#include <util/io/fd_share.h>
+
+#include <util/unittests/check_valgrind.h>
+#include <util/unittests/fork.h>
+
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <cstdio>
+
+using namespace isc::util::io;
+using namespace isc::util::unittests;
+
+namespace {
+
+// We test that we can transfer a pipe over other pipe
+TEST(FDShare, transfer) {
+
+ if (!isc::util::unittests::runningOnValgrind()) {
+ // Get a pipe and fork
+ int pipes[2];
+ ASSERT_NE(-1, socketpair(AF_UNIX, SOCK_STREAM, 0, pipes));
+ const pid_t sender(fork());
+ ASSERT_NE(-1, sender);
+ if (sender) { // We are in parent
+ // Close the other side of pipe, we want only writable one
+ EXPECT_NE(-1, close(pipes[0]));
+ // Get a process to check data
+ int fd(0);
+ const pid_t checker(check_output(&fd, "data", 4));
+ ASSERT_NE(-1, checker);
+ // Now, send the file descriptor, close it and close the pipe
+ EXPECT_NE(-1, send_fd(pipes[1], fd));
+ EXPECT_NE(-1, close(pipes[1]));
+ EXPECT_NE(-1, close(fd));
+ // Check both subprocesses ended well
+ EXPECT_TRUE(process_ok(sender));
+ EXPECT_TRUE(process_ok(checker));
+ } else { // We are in child. We do not use ASSERT here
+ // Close the write end, we only read
+ if (close(pipes[1])) {
+ exit(1);
+ }
+ // Get the file descriptor
+ const int fd(recv_fd(pipes[0]));
+ if (fd == -1) {
+ exit(1);
+ }
+ // This pipe is not needed
+ if (close(pipes[0])) {
+ exit(1);
+ }
+ // Send "data" through the received fd, close it and be done
+ if (!write_data(fd, "data", 4) || close(fd) == -1) {
+ exit(1);
+ }
+ exit(0);
+ }
+ }
+}
+
+}
diff --git a/src/lib/util/tests/fd_tests.cc b/src/lib/util/tests/fd_tests.cc
new file mode 100644
index 0000000..eee597c
--- /dev/null
+++ b/src/lib/util/tests/fd_tests.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/unittests/check_valgrind.h>
+
+#include <util/io/fd.h>
+
+#include <util/unittests/fork.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util::io;
+using namespace isc::util::unittests;
+
+namespace {
+
+// Make sure the test is large enough and does not fit into one
+// read or write request
+const size_t TEST_DATA_SIZE = 8 * 1024 * 1024;
+
+class FDTest : public ::testing::Test {
+public:
+ unsigned char *data, *buffer;
+
+ /// @brief Constructor
+ FDTest() :
+ // We do not care what is inside, we just need it to be the same
+ data(new unsigned char[TEST_DATA_SIZE]),
+ buffer(NULL) {
+ memset(data, 0, TEST_DATA_SIZE);
+ }
+
+ /// @brief Destructor
+ ~FDTest() {
+ delete[] data;
+ delete[] buffer;
+ }
+};
+
+// Test we read what was sent
+TEST_F(FDTest, read) {
+ if (!isc::util::unittests::runningOnValgrind()) {
+ int read_pipe(0);
+ buffer = new unsigned char[TEST_DATA_SIZE];
+ pid_t feeder(provide_input(&read_pipe, data, TEST_DATA_SIZE));
+ ASSERT_GE(feeder, 0);
+ ssize_t received(read_data(read_pipe, buffer, TEST_DATA_SIZE));
+ EXPECT_TRUE(process_ok(feeder));
+ EXPECT_EQ(TEST_DATA_SIZE, received);
+ EXPECT_EQ(0, memcmp(data, buffer, received));
+ }
+}
+
+// Test we write the correct thing
+TEST_F(FDTest, write) {
+ if (!isc::util::unittests::runningOnValgrind()) {
+ int write_pipe(0);
+ pid_t checker(check_output(&write_pipe, data, TEST_DATA_SIZE));
+ ASSERT_GE(checker, 0);
+ EXPECT_TRUE(write_data(write_pipe, data, TEST_DATA_SIZE));
+ EXPECT_EQ(0, close(write_pipe));
+ EXPECT_TRUE(process_ok(checker));
+ }
+}
+
+}
diff --git a/src/lib/util/tests/file_utilities_unittest.cc b/src/lib/util/tests/file_utilities_unittest.cc
new file mode 100644
index 0000000..4ee9093
--- /dev/null
+++ b/src/lib/util/tests/file_utilities_unittest.cc
@@ -0,0 +1,86 @@
+// Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <util/file_utilities.h>
+#include <gtest/gtest.h>
+#include <fstream>
+
+using namespace isc;
+using namespace isc::util::file;
+using namespace std;
+
+namespace {
+
+/// @brief Test fixture class for testing operations on files.
+class FileUtilTest : public ::testing::Test {
+public:
+
+ /// @brief Destructor.
+ ///
+ /// Deletes the test file if any.
+ virtual ~FileUtilTest();
+};
+
+FileUtilTest::~FileUtilTest() {
+ string test_file_name = string(TEST_DATA_BUILDDIR) + "/fu.test";
+ static_cast<void>(remove(test_file_name.c_str()));
+}
+
+/// @brief Check an error is returned by getContent on not existent file.
+TEST_F(FileUtilTest, notExists) {
+ string file_name("/this/does/not/exists");
+ try {
+ string c = getContent(file_name);
+ FAIL() << "this test must throw before this line";
+ } catch (const BadValue& ex) {
+ string expected = "can't open file '" + file_name;
+ expected += "': No such file or directory";
+ EXPECT_EQ(string(ex.what()), expected);
+ } catch (const std::exception& ex) {
+ FAIL() << "unexpected exception: " << ex.what();
+ }
+}
+
+/// @note No easy can't stat.
+
+/// @brief Check an error is returned by getContent on not regular file.
+TEST_F(FileUtilTest, notRegular) {
+ string file_name("/");
+ try {
+ string c = getContent(file_name);
+ FAIL() << "this test must throw before this line";
+ } catch (const BadValue& ex) {
+ string expected = "'" + file_name + "' must be a regular file";
+ EXPECT_EQ(string(ex.what()), expected);
+ } catch (const std::exception& ex) {
+ FAIL() << "unexpected exception: " << ex.what();
+ }
+}
+
+/// @brief Check getContent works.
+TEST_F(FileUtilTest, basic) {
+ string file_name = string(TEST_DATA_BUILDDIR) + "/fu.test";
+ ofstream fs(file_name.c_str(), ofstream::out | ofstream::trunc);
+ ASSERT_TRUE(fs.is_open());
+ fs << "abdc";
+ fs.close();
+ string content;
+ EXPECT_NO_THROW(content = getContent(file_name));
+ EXPECT_EQ("abdc", content);
+}
+
+/// @brief Check isDir works.
+TEST_F(FileUtilTest, isDir) {
+ EXPECT_TRUE(isDir("/dev"));
+ EXPECT_FALSE(isDir("/dev/null"));
+ EXPECT_FALSE(isDir("/this/does/not/exists"));
+ EXPECT_FALSE(isDir("/etc/hosts"));
+}
+
+}
diff --git a/src/lib/util/tests/filename_unittest.cc b/src/lib/util/tests/filename_unittest.cc
new file mode 100644
index 0000000..39ff1f9
--- /dev/null
+++ b/src/lib/util/tests/filename_unittest.cc
@@ -0,0 +1,225 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <util/filename.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace std;
+
+class FilenameTest : public ::testing::Test {
+protected:
+ FilenameTest()
+ {
+ }
+};
+
+
+// Check that the name can be changed
+
+TEST_F(FilenameTest, SetName) {
+ Filename fname("/a/b/c.d");
+ EXPECT_EQ("/a/b/c.d", fname.fullName());
+
+ fname.setName("test.txt");
+ EXPECT_EQ("test.txt", fname.fullName());
+}
+
+
+// Check that the components are split correctly. This is a check of the
+// private member split() method.
+
+TEST_F(FilenameTest, Components) {
+
+ // Complete name
+ Filename fname("/alpha/beta/gamma.delta");
+ EXPECT_EQ("/alpha/beta/", fname.directory());
+ EXPECT_EQ("gamma", fname.name());
+ EXPECT_EQ(".delta", fname.extension());
+ EXPECT_EQ("gamma.delta", fname.nameAndExtension());
+
+ // Directory only
+ fname.setName("/gamma/delta/");
+ EXPECT_EQ("/gamma/delta/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("", fname.nameAndExtension());
+
+ // Filename only
+ fname.setName("epsilon");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("epsilon", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("epsilon", fname.nameAndExtension());
+
+ // Extension only
+ fname.setName(".zeta");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".zeta", fname.extension());
+ EXPECT_EQ(".zeta", fname.nameAndExtension());
+
+ // Missing directory
+ fname.setName("eta.theta");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("eta", fname.name());
+ EXPECT_EQ(".theta", fname.extension());
+ EXPECT_EQ("eta.theta", fname.nameAndExtension());
+
+ // Missing filename
+ fname.setName("/iota/.kappa");
+ EXPECT_EQ("/iota/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".kappa", fname.extension());
+ EXPECT_EQ(".kappa", fname.nameAndExtension());
+
+ // Missing extension
+ fname.setName("lambda/mu/nu");
+ EXPECT_EQ("lambda/mu/", fname.directory());
+ EXPECT_EQ("nu", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("nu", fname.nameAndExtension());
+
+ // Check that the decomposition can occur in the presence of leading and
+ // trailing spaces
+ fname.setName(" lambda/mu/nu\t ");
+ EXPECT_EQ("lambda/mu/", fname.directory());
+ EXPECT_EQ("nu", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("nu", fname.nameAndExtension());
+
+ // Empty string
+ fname.setName("");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("", fname.nameAndExtension());
+
+ // ... and just spaces
+ fname.setName(" ");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("", fname.nameAndExtension());
+
+ // Check corner cases - where separators are present, but strings are
+ // absent.
+ fname.setName("/");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("", fname.nameAndExtension());
+
+ fname.setName(".");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".", fname.extension());
+ EXPECT_EQ(".", fname.nameAndExtension());
+
+ fname.setName("/.");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".", fname.extension());
+ EXPECT_EQ(".", fname.nameAndExtension());
+
+ // Note that the space is a valid filename here; only leading and trailing
+ // spaces should be trimmed.
+ fname.setName("/ .");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ(" ", fname.name());
+ EXPECT_EQ(".", fname.extension());
+ EXPECT_EQ(" .", fname.nameAndExtension());
+
+ fname.setName(" / . ");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ(" ", fname.name());
+ EXPECT_EQ(".", fname.extension());
+ EXPECT_EQ(" .", fname.nameAndExtension());
+}
+
+// Check that the expansion with a default works.
+
+TEST_F(FilenameTest, ExpandWithDefault) {
+ Filename fname("a.b");
+
+ // These tests also check that the trimming of the default component is
+ // done properly.
+ EXPECT_EQ("/c/d/a.b", fname.expandWithDefault(" /c/d/ "));
+ EXPECT_EQ("/c/d/a.b", fname.expandWithDefault("/c/d/e.f"));
+ EXPECT_EQ("a.b", fname.expandWithDefault("e.f"));
+
+ fname.setName("/a/b/c");
+ EXPECT_EQ("/a/b/c.d", fname.expandWithDefault(".d"));
+ EXPECT_EQ("/a/b/c.d", fname.expandWithDefault("x.d"));
+ EXPECT_EQ("/a/b/c.d", fname.expandWithDefault("/s/t/u.d"));
+ EXPECT_EQ("/a/b/c", fname.expandWithDefault("/s/t/u"));
+
+ fname.setName(".h");
+ EXPECT_EQ("/a/b/c.h", fname.expandWithDefault("/a/b/c.msg"));
+}
+
+// Check that we can use this as a default in expanding a filename
+
+TEST_F(FilenameTest, UseAsDefault) {
+
+ Filename fname("a.b");
+
+ // These tests also check that the trimming of the default component is
+ // done properly.
+ EXPECT_EQ("/c/d/a.b", fname.useAsDefault(" /c/d/ "));
+ EXPECT_EQ("/c/d/e.f", fname.useAsDefault("/c/d/e.f"));
+ EXPECT_EQ("e.f", fname.useAsDefault("e.f"));
+
+ fname.setName("/a/b/c");
+ EXPECT_EQ("/a/b/c.d", fname.useAsDefault(".d"));
+ EXPECT_EQ("/a/b/x.d", fname.useAsDefault("x.d"));
+ EXPECT_EQ("/s/t/u.d", fname.useAsDefault("/s/t/u.d"));
+ EXPECT_EQ("/s/t/u", fname.useAsDefault("/s/t/u"));
+ EXPECT_EQ("/a/b/c", fname.useAsDefault(""));
+}
+
+TEST_F(FilenameTest, setDirectory) {
+ Filename fname("a.b");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("a.b", fname.fullName());
+ EXPECT_EQ("a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("/just/some/dir/");
+ EXPECT_EQ("/just/some/dir/", fname.directory());
+ EXPECT_EQ("/just/some/dir/a.b", fname.fullName());
+ EXPECT_EQ("/just/some/dir/a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("/just/some/dir");
+ EXPECT_EQ("/just/some/dir/", fname.directory());
+ EXPECT_EQ("/just/some/dir/a.b", fname.fullName());
+ EXPECT_EQ("/just/some/dir/a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("/");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ("/a.b", fname.fullName());
+ EXPECT_EQ("/a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("a.b", fname.fullName());
+ EXPECT_EQ("a.b", fname.expandWithDefault(""));
+
+ fname = Filename("/first/a.b");
+ EXPECT_EQ("/first/", fname.directory());
+ EXPECT_EQ("/first/a.b", fname.fullName());
+ EXPECT_EQ("/first/a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("/just/some/dir");
+ EXPECT_EQ("/just/some/dir/", fname.directory());
+ EXPECT_EQ("/just/some/dir/a.b", fname.fullName());
+ EXPECT_EQ("/just/some/dir/a.b", fname.expandWithDefault(""));
+}
diff --git a/src/lib/util/tests/hash_unittest.cc b/src/lib/util/tests/hash_unittest.cc
new file mode 100644
index 0000000..f789e51
--- /dev/null
+++ b/src/lib/util/tests/hash_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/hash.h>
+
+#include <gtest/gtest.h>
+
+#include <cstring>
+#include <vector>
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+TEST(HashTest, empty) {
+ EXPECT_EQ(14695981039346656037ull, Hash64::hash(0, 0));
+}
+
+TEST(HashTest, foobar) {
+ EXPECT_EQ(9625390261332436968ull, Hash64::hash(string("foobar")));
+}
+
+TEST(HashTest, chongo) {
+ EXPECT_EQ(5080352029159061781ull,
+ Hash64::hash(string("chongo was here!\n")));
+}
+
+}
diff --git a/src/lib/util/tests/hex_unittest.cc b/src/lib/util/tests/hex_unittest.cc
new file mode 100644
index 0000000..1e611b5
--- /dev/null
+++ b/src/lib/util/tests/hex_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <stdint.h>
+
+#include <vector>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <util/encode/hex.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util::encode;
+
+namespace {
+const string hex_txt("DEADBEEFDECADE");
+const string hex_txt_space("DEAD BEEF DECADE");
+const string hex_txt_lower("deadbeefdecade");
+
+class HexTest : public ::testing::Test {
+protected:
+ HexTest() : encoding_chars("0123456789ABCDEF") {}
+ vector<uint8_t> decoded_data;
+ const string encoding_chars;
+};
+
+TEST_F(HexTest, encodeHex) {
+ std::vector<uint8_t> data;
+
+ data.push_back(0xde);
+ data.push_back(0xad);
+ data.push_back(0xbe);
+ data.push_back(0xef);
+ data.push_back(0xde);
+ data.push_back(0xca);
+ data.push_back(0xde);
+ EXPECT_EQ(hex_txt, encodeHex(data));
+}
+
+void
+compareData(const std::vector<uint8_t>& data) {
+ EXPECT_EQ(0xde, data[0]);
+ EXPECT_EQ(0xad, data[1]);
+ EXPECT_EQ(0xbe, data[2]);
+ EXPECT_EQ(0xef, data[3]);
+ EXPECT_EQ(0xde, data[4]);
+ EXPECT_EQ(0xca, data[5]);
+ EXPECT_EQ(0xde, data[6]);
+}
+
+TEST_F(HexTest, decodeHex) {
+ std::vector<uint8_t> result;
+
+ decodeHex(hex_txt, result);
+ compareData(result);
+
+ // lower case hex digits should be accepted
+ result.clear();
+ decodeHex(hex_txt_lower, result);
+ compareData(result);
+
+ // white space should be ignored
+ result.clear();
+ decodeHex(hex_txt_space, result);
+ compareData(result);
+
+ // Bogus input: should fail
+ result.clear();
+ EXPECT_THROW(decodeHex("1x", result), BadValue);
+
+ // Bogus input: encoded string must have an even number of characters.
+ result.clear();
+ EXPECT_THROW(decodeHex("dea", result), BadValue);
+}
+
+// For Hex encode/decode we use handmade mappings, so it's prudent to test the
+// entire mapping table explicitly.
+TEST_F(HexTest, decodeMap) {
+ string input("00"); // input placeholder
+
+ // See Base32HexTest.decodeMap for details of the following tests.
+ for (int i = 0; i < 256; ++i) {
+ input[1] = i;
+
+ const char ch = toupper(i);
+ const size_t pos = encoding_chars.find(ch);
+ if (pos == string::npos) {
+ EXPECT_THROW(decodeHex(input, decoded_data), BadValue);
+ } else {
+ decodeHex(input, decoded_data);
+ EXPECT_EQ(1, decoded_data.size());
+ EXPECT_EQ(pos, decoded_data[0]);
+ }
+ }
+}
+
+TEST_F(HexTest, encodeMap) {
+ for (uint8_t i = 0; i < 16; ++i) {
+ decoded_data.clear();
+ decoded_data.push_back(i);
+ EXPECT_EQ(encoding_chars[i], encodeHex(decoded_data)[1]);
+ }
+}
+
+}
diff --git a/src/lib/util/tests/io_utilities_unittest.cc b/src/lib/util/tests/io_utilities_unittest.cc
new file mode 100644
index 0000000..acaaf13
--- /dev/null
+++ b/src/lib/util/tests/io_utilities_unittest.cc
@@ -0,0 +1,160 @@
+// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// \brief Test of asiolink utilities
+///
+/// Tests the functionality of the asiolink utilities code by comparing them
+/// with the equivalent methods in isc::dns::[Input/Output]Buffer.
+
+#include <config.h>
+
+#include <cstddef>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+using namespace isc::util;
+
+TEST(asioutil, readUint16) {
+
+ // Reference buffer
+ uint8_t data[2] = {0, 0};
+ InputBuffer buffer(data, sizeof(data));
+
+ // Avoid possible compiler warnings by only setting uint8_t variables to
+ // uint8_t values.
+ uint8_t i8 = 0;
+ uint8_t j8 = 0;
+ for (int i = 0; i < (2 << 8); ++i, ++i8) {
+ for (int j = 0; j < (2 << 8); ++j, ++j8) {
+ data[0] = i8;
+ data[1] = j8;
+ buffer.setPosition(0);
+ EXPECT_EQ(buffer.readUint16(), readUint16(data, sizeof(data)));
+ }
+ }
+}
+
+TEST(asioutil, readUint16OutOfRange) {
+ uint8_t data = 0;
+ EXPECT_THROW(readUint16(&data, sizeof(data)), isc::OutOfRange);
+}
+
+TEST(asioutil, writeUint16) {
+
+ // Reference buffer
+ OutputBuffer buffer(2);
+ uint8_t test[2];
+
+ // Avoid possible compiler warnings by only setting uint16_t variables to
+ // uint16_t values.
+ uint16_t i16 = 0;
+ for (uint32_t i = 0; i < (2 << 16); ++i, ++i16) {
+
+ // Write the reference data
+ buffer.clear();
+ buffer.writeUint16(i16);
+
+ // ... and the test data
+ writeUint16(i16, test, sizeof(test));
+
+ // ... and compare
+ const uint8_t* ref = static_cast<const uint8_t*>(buffer.getData());
+ EXPECT_EQ(ref[0], test[0]);
+ EXPECT_EQ(ref[1], test[1]);
+ }
+}
+
+TEST(asioutil, writeUint16OutOfRange) {
+ uint16_t i16 = 42;
+ uint8_t data;
+ EXPECT_THROW(writeUint16(i16, &data, sizeof(data)), isc::OutOfRange);
+}
+
+// test data shared amount readUint32 and writeUint32 tests
+const static uint32_t test32[] = {
+ 0,
+ 1,
+ 2000,
+ 0x80000000,
+ 0xffffffff
+};
+
+TEST(asioutil, readUint32) {
+ uint8_t data[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+
+ // make sure that we can read data, regardless of
+ // the memory alignment. That' why we need to repeat
+ // it 4 times.
+ for (int offset=0; offset < 4; offset++) {
+ for (int i=0; i < sizeof(test32)/sizeof(uint32_t); i++) {
+ uint32_t tmp = htonl(test32[i]);
+ memcpy(&data[offset], &tmp, sizeof(uint32_t));
+
+ EXPECT_EQ(test32[i], readUint32(&data[offset], sizeof(uint32_t)));
+ }
+ }
+}
+
+TEST(asioutil, readUint32OutOfRange) {
+ uint8_t data[3] = {0, 0, 0};
+ EXPECT_THROW(readUint32(data, sizeof(data)), isc::OutOfRange);
+}
+
+TEST(asioutil, writeUint32) {
+ uint8_t data[8];
+
+ // make sure that we can write data, regardless of
+ // the memory alignment. That's why we need to repeat
+ // it 4 times.
+ for (int offset=0; offset < 4; offset++) {
+ for (int i=0; i < sizeof(test32)/sizeof(uint32_t); i++) {
+ uint8_t* ptr = writeUint32(test32[i], &data[offset],
+ sizeof(uint32_t));
+
+ EXPECT_EQ(&data[offset]+sizeof(uint32_t), ptr);
+
+ uint32_t tmp = htonl(test32[i]);
+
+ EXPECT_EQ(0, memcmp(&tmp, &data[offset], sizeof(uint32_t)));
+ }
+ }
+}
+
+TEST(asioutil, writeUint32OutOfRange) {
+ uint32_t i32 = 28;
+ uint8_t data[3];
+ EXPECT_THROW(writeUint32(i32, data, sizeof(data)), isc::OutOfRange);
+}
+
+// Tests whether uint64 can be read from a buffer properly.
+TEST(asioutil, readUint64) {
+
+ uint8_t buf[8];
+ for (int offset = 0; offset < sizeof(buf); offset++) {
+ buf[offset] = offset+1;
+ }
+
+ // Let's do some simple sanity checks first.
+ EXPECT_THROW(readUint64(NULL, 0), isc::OutOfRange);
+ EXPECT_THROW(readUint64(buf, 7), isc::OutOfRange);
+
+ // Now check if a real value could be read.
+ const uint64_t exp_val = 0x0102030405060708ul;
+ uint64_t val;
+
+ EXPECT_NO_THROW(val = readUint64(buf, 8));
+ EXPECT_EQ(val, exp_val);
+
+ // Now check if there are no buffer overflows.
+ memset(buf, 0xff, 8);
+
+ EXPECT_NO_THROW(val = readUint64(buf, 8));
+ EXPECT_EQ(0xfffffffffffffffful, val);
+}
diff --git a/src/lib/util/tests/labeled_value_unittest.cc b/src/lib/util/tests/labeled_value_unittest.cc
new file mode 100644
index 0000000..c994156
--- /dev/null
+++ b/src/lib/util/tests/labeled_value_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/labeled_value.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Verifies basic construction and accessors for LabeledValue.
+TEST(LabeledValue, construction) {
+ /// Verify that an empty label is not allowed.
+ ASSERT_THROW(LabeledValue(1, ""), LabeledValueError);
+
+ /// Verify that a valid constructor works.
+ LabeledValuePtr lvp;
+ ASSERT_NO_THROW(lvp.reset(new LabeledValue(1, "NotBlank")));
+ ASSERT_TRUE(lvp);
+
+ // Verify that the value can be accessed.
+ EXPECT_EQ(1, lvp->getValue());
+
+ // Verify that the label can be accessed.
+ EXPECT_EQ("NotBlank", lvp->getLabel());
+}
+
+/// @brief Verifies the logical operators defined for LabeledValue.
+TEST(LabeledValue, operators) {
+ LabeledValuePtr lvp1;
+ LabeledValuePtr lvp1Also;
+ LabeledValuePtr lvp2;
+
+ // Create three instances, two of which have the same numeric value.
+ ASSERT_NO_THROW(lvp1.reset(new LabeledValue(1, "One")));
+ ASSERT_NO_THROW(lvp1Also.reset(new LabeledValue(1, "OneAlso")));
+ ASSERT_NO_THROW(lvp2.reset(new LabeledValue(2, "Two")));
+
+ // Verify each of the operators.
+ EXPECT_TRUE(*lvp1 == *lvp1Also);
+ EXPECT_TRUE(*lvp1 != *lvp2);
+ EXPECT_TRUE(*lvp1 < *lvp2);
+ EXPECT_FALSE(*lvp2 < *lvp1);
+}
+
+/// @brief Verifies the default constructor for LabeledValueSet.
+TEST(LabeledValueSet, construction) {
+ ASSERT_NO_THROW (LabeledValueSet());
+}
+
+/// @brief Verifies the basic operations of a LabeledValueSet.
+/// Essentially we verify that we can define a set of valid entries and
+/// look them up without issue.
+TEST(LabeledValueSet, basicOperation) {
+ const char* labels[] = {"Zero", "One", "Two", "Three" };
+ LabeledValueSet lvset;
+ LabeledValuePtr lvp;
+
+ // Verify the we cannot add an empty pointer to the set.
+ EXPECT_THROW(lvset.add(lvp), LabeledValueError);
+
+ // Verify that we can add an entry to the set via pointer.
+ ASSERT_NO_THROW(lvp.reset(new LabeledValue(0, labels[0])));
+ EXPECT_NO_THROW(lvset.add(lvp));
+
+ // Verify that we cannot add a duplicate entry.
+ EXPECT_THROW(lvset.add(lvp), LabeledValueError);
+
+ // Add the remaining entries using add(int,char*) variant.
+ for (int i = 1; i < 3; i++) {
+ EXPECT_NO_THROW(lvset.add(i, labels[i]));
+ }
+
+ // Verify that we can't add a duplicate entry this way either.
+ EXPECT_THROW ((lvset.add(0, labels[0])), LabeledValueError);
+
+ // Verify that we can look up all of the defined entries properly.
+ for (int i = 1; i < 3; i++) {
+ EXPECT_TRUE(lvset.isDefined(i));
+ EXPECT_NO_THROW(lvp = lvset.get(i));
+ EXPECT_EQ(lvp->getValue(), i);
+ EXPECT_EQ(lvp->getLabel(), labels[i]);
+ EXPECT_EQ(lvset.getLabel(i), labels[i]);
+ }
+
+ // Verify behavior for a value that is not defined.
+ EXPECT_FALSE(lvset.isDefined(4));
+ EXPECT_NO_THROW(lvp = lvset.get(4));
+ EXPECT_FALSE(lvp);
+ EXPECT_EQ(lvset.getLabel(4), LabeledValueSet::UNDEFINED_LABEL);
+}
+
+}
diff --git a/src/lib/util/tests/memory_segment_common_unittest.cc b/src/lib/util/tests/memory_segment_common_unittest.cc
new file mode 100644
index 0000000..f95021d
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_common_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/memory_segment.h>
+
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <cstring>
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+namespace test {
+
+void
+checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok) {
+ // NULL name is not allowed.
+ EXPECT_THROW(segment.getNamedAddress(NULL), InvalidParameter);
+
+ // If the name does not exist, false should be returned.
+ EXPECT_FALSE(segment.getNamedAddress("test address").first);
+
+ // Now set it
+ void* ptr32 = segment.allocate(sizeof(uint32_t));
+ const uint32_t test_val = 42;
+ *static_cast<uint32_t*>(ptr32) = test_val;
+ EXPECT_FALSE(segment.setNamedAddress("test address", ptr32));
+
+ // NULL name isn't allowed.
+ EXPECT_THROW(segment.setNamedAddress(NULL, ptr32), InvalidParameter);
+ EXPECT_THROW(segment.getNamedAddress(NULL), InvalidParameter);
+ EXPECT_THROW(segment.clearNamedAddress(NULL), InvalidParameter);
+
+ // Empty names are not allowed.
+ EXPECT_THROW(segment.setNamedAddress("", ptr32), InvalidParameter);
+ EXPECT_THROW(segment.getNamedAddress(""), InvalidParameter);
+ EXPECT_THROW(segment.clearNamedAddress(""), InvalidParameter);
+
+ // Names beginning with _ are not allowed.
+ EXPECT_THROW(segment.setNamedAddress("_foo", ptr32), InvalidParameter);
+ EXPECT_THROW(segment.getNamedAddress("_foo"), InvalidParameter);
+ EXPECT_THROW(segment.clearNamedAddress("_foo"), InvalidParameter);
+
+ // we can now get it; the stored value should be intact.
+ MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress("test address");
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ(test_val, *static_cast<const uint32_t*>(result.second));
+
+ // Override it.
+ void* ptr16 = segment.allocate(sizeof(uint16_t));
+ const uint16_t test_val16 = 4200;
+ *static_cast<uint16_t*>(ptr16) = test_val16;
+ EXPECT_FALSE(segment.setNamedAddress("test address", ptr16));
+ result = segment.getNamedAddress("test address");
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));
+
+ // Clear it. Then we won't be able to find it any more.
+ EXPECT_TRUE(segment.clearNamedAddress("test address"));
+ EXPECT_FALSE(segment.getNamedAddress("test address").first);
+
+ // duplicate attempt of clear will result in false as it doesn't exist.
+ EXPECT_FALSE(segment.clearNamedAddress("test address"));
+
+ // Setting NULL is okay.
+ EXPECT_FALSE(segment.setNamedAddress("null address", NULL));
+ result = segment.getNamedAddress("null address");
+ EXPECT_TRUE(result.first);
+ EXPECT_FALSE(result.second);
+
+ // If the underlying implementation performs explicit check against
+ // out-of-segment address, confirm the behavior.
+ if (!out_of_segment_ok) {
+ uint8_t ch = 'A';
+ EXPECT_THROW(segment.setNamedAddress("local address", &ch),
+ MemorySegmentError);
+ }
+
+ // clean them up all
+ segment.deallocate(ptr32, sizeof(uint32_t));
+ EXPECT_FALSE(segment.allMemoryDeallocated()); // not fully deallocated
+ segment.deallocate(ptr16, sizeof(uint16_t)); // not yet
+ EXPECT_FALSE(segment.allMemoryDeallocated());
+ EXPECT_TRUE(segment.clearNamedAddress("null address"));
+ // null name isn't allowed:
+ EXPECT_THROW(segment.clearNamedAddress(NULL), InvalidParameter);
+ EXPECT_TRUE(segment.allMemoryDeallocated()); // now everything is gone
+}
+
+}
+}
+}
diff --git a/src/lib/util/tests/memory_segment_common_unittest.h b/src/lib/util/tests/memory_segment_common_unittest.h
new file mode 100644
index 0000000..435ff12
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_common_unittest.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <util/memory_segment.h>
+
+namespace isc {
+namespace util {
+namespace test {
+
+/// \brief Implementation dependent checks on memory segment named addresses.
+///
+/// This function contains a set of test cases for given memory segment
+/// regarding "named address" methods. The test cases basically only depend
+/// on the base class interfaces, but if the underlying implementation does
+/// not check if the given address to setNamedAddress() belongs to the segment,
+/// out_of_segment_ok should be set to true.
+void checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok);
+
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/tests/memory_segment_local_unittest.cc b/src/lib/util/tests/memory_segment_local_unittest.cc
new file mode 100644
index 0000000..d1aa52d
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_local_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/tests/memory_segment_common_unittest.h>
+
+#include <util/memory_segment_local.h>
+#include <exceptions/exceptions.h>
+#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+#include <memory>
+#include <limits.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace {
+
+TEST(MemorySegmentLocal, TestLocal) {
+ boost::scoped_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+ // By default, nothing is allocated.
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+
+ void* ptr = segment->allocate(1024);
+
+ // Now, we have an allocation:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ void* ptr2 = segment->allocate(42);
+
+ // Still:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ // These should not fail, because the buffers have been allocated.
+ EXPECT_NO_FATAL_FAILURE(memset(ptr, 0, 1024));
+ EXPECT_NO_FATAL_FAILURE(memset(ptr, 0, 42));
+
+ segment->deallocate(ptr, 1024);
+
+ // Still:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ segment->deallocate(ptr2, 42);
+
+ // Now, we have an deallocated everything:
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+}
+
+/// @todo: disabled, see ticket #3510
+TEST(MemorySegmentLocal, DISABLED_TestTooMuchMemory) {
+ boost::scoped_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+ // Although it should be perfectly fine to use the ULONG_MAX
+ // instead of LONG_MAX as the size_t value should be unsigned,
+ // Valgrind appears to be using the signed value and hence the
+ // maximum positive value is LONG_MAX for Valgrind. But, this
+ // should be sufficient to test the "too much memory" conditions.
+ EXPECT_THROW(segment->allocate(LONG_MAX), bad_alloc);
+}
+
+TEST(MemorySegmentLocal, TestBadDeallocate) {
+ boost::scoped_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+ // By default, nothing is allocated.
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+
+ void* ptr = segment->allocate(1024);
+
+ // Now, we have an allocation:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ // This should not throw
+ EXPECT_NO_THROW(segment->deallocate(ptr, 1024));
+
+ // Now, we have an deallocated everything:
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+
+ ptr = segment->allocate(1024);
+
+ // Now, we have another allocation:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ // This should throw as the size passed to deallocate() is larger
+ // than what was allocated.
+ EXPECT_THROW(segment->deallocate(ptr, 2048), isc::OutOfRange);
+
+ // This should not throw
+ EXPECT_NO_THROW(segment->deallocate(ptr, 1024));
+
+ // Now, we have an deallocated everything:
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+}
+
+TEST(MemorySegmentLocal, TestNullDeallocate) {
+ boost::scoped_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+ // By default, nothing is allocated.
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+
+ // NULL deallocation is a no-op.
+ EXPECT_NO_THROW(segment->deallocate(NULL, 1024));
+
+ // This should still return true.
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+}
+
+TEST(MemorySegmentLocal, namedAddress) {
+ MemorySegmentLocal segment;
+ isc::util::test::checkSegmentNamedAddress(segment, true);
+}
+
+} // anonymous namespace
diff --git a/src/lib/util/tests/multi_threading_mgr_unittest.cc b/src/lib/util/tests/multi_threading_mgr_unittest.cc
new file mode 100644
index 0000000..4d3ae33
--- /dev/null
+++ b/src/lib/util/tests/multi_threading_mgr_unittest.cc
@@ -0,0 +1,636 @@
+// Copyright (C) 2019-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util;
+using namespace isc;
+
+/// @brief Fixture used to reset multi-threading before and after each test.
+struct MultiThreadingMgrTest : ::testing::Test {
+ MultiThreadingMgrTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+ ~MultiThreadingMgrTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+};
+
+/// @brief Verifies that the default mode is false (MT disabled).
+TEST_F(MultiThreadingMgrTest, defaultMode) {
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+}
+
+/// @brief Verifies that the mode setter works.
+TEST_F(MultiThreadingMgrTest, setMode) {
+ // enable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(true));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(false));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+}
+
+/// @brief Verifies that accessing the thread pool works.
+TEST_F(MultiThreadingMgrTest, threadPool) {
+ // get the thread pool
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().getThreadPool());
+}
+
+/// @brief Verifies that the thread pool size setter works.
+TEST_F(MultiThreadingMgrTest, threadPoolSize) {
+ // default thread count is 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // set thread count to 16
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setThreadPoolSize(16));
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // set thread count to 0
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setThreadPoolSize(0));
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+}
+
+/// @brief Verifies that the packet queue size setter works.
+TEST_F(MultiThreadingMgrTest, packetQueueSize) {
+ // default queue size is 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 0);
+ // set queue size to 16
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setPacketQueueSize(16));
+ // queue size should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 16);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 16);
+ // set queue size to 0
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setPacketQueueSize(0));
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 0);
+}
+
+/// @brief Verifies that detecting thread count works.
+TEST_F(MultiThreadingMgrTest, detectThreadCount) {
+ // detecting thread count should work
+ EXPECT_NE(MultiThreadingMgr::detectThreadCount(), 0);
+}
+
+/// @brief Verifies that apply settings works.
+TEST_F(MultiThreadingMgrTest, applyConfig) {
+ // get the thread pool
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // default thread count is 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // enable MT with 16 threads and queue size 256
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 16, 256));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // thread pool should be started
+ EXPECT_EQ(thread_pool.size(), 16);
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 16, 256));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // enable MT with auto scaling
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 0, 0));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // thread count should be detected automatically
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), MultiThreadingMgr::detectThreadCount());
+ // thread pool should be started
+ EXPECT_EQ(thread_pool.size(), MultiThreadingMgr::detectThreadCount());
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 0, 0));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+}
+
+/// @brief Verifies that the critical section flag works.
+TEST_F(MultiThreadingMgrTest, criticalSectionFlag) {
+ // get the thread pool
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // exit critical section
+ EXPECT_THROW(MultiThreadingMgr::instance().exitCriticalSection(), InvalidOperation);
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // enter critical section
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().enterCriticalSection());
+ // critical section should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().isInCriticalSection());
+ // enable MT with 16 threads and queue size 256
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 16, 256));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // exit critical section
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().exitCriticalSection());
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // exit critical section
+ EXPECT_THROW(MultiThreadingMgr::instance().exitCriticalSection(), InvalidOperation);
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 0, 0));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+}
+
+/// @brief Verifies that the critical section works.
+TEST_F(MultiThreadingMgrTest, criticalSection) {
+ // get the thread pool instance
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // apply multi-threading configuration with 16 threads and queue size 256
+ MultiThreadingMgr::instance().apply(true, 16, 256);
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 16);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection inner_cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ }
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 16);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // apply multi-threading configuration with 64 threads and queue size 4
+ MultiThreadingMgr::instance().apply(true, 64, 4);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 4
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 4);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 64);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 4
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 4);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 4
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 4);
+ // apply multi-threading configuration with 0 threads
+ MultiThreadingMgr::instance().apply(false, 64, 256);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection inner_cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ }
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // apply multi-threading configuration with 64 threads
+ MultiThreadingMgr::instance().apply(true, 64, 256);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 64);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // apply multi-threading configuration with 0 threads
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+}
+
+/// @brief Checks that the lock works only when multi-threading is enabled and
+/// only during its lifetime.
+TEST(MultiThreadingLockTest, scope) {
+ // Check that the mutex is unlocked by default at first.
+ std::mutex mutex;
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(false));
+
+ // Check that the lock does not locks the mutex if multi-threading is disabled.
+ {
+ MultiThreadingLock lock(mutex);
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+ }
+
+ // Check that the mutex is still unlocked when the lock goes out of scope.
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(true));
+
+ // Check that the lock actively locks the mutex if multi-threading is enabled.
+ {
+ MultiThreadingLock lock(mutex);
+ ASSERT_FALSE(mutex.try_lock());
+ }
+
+ // Check that the mutex is unlocked when the lock goes out of scope.
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+}
+
+/// @brief Test fixture for exercised CriticalSection callbacks.
+class CriticalSectionCallbackTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ CriticalSectionCallbackTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+
+ /// @brief Destructor.
+ ~CriticalSectionCallbackTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+
+ /// @brief A callback that adds the value 1 to invocations lists.
+ void one() {
+ invocations_.push_back(1);
+ }
+
+ /// @brief A callback that adds the value 2 to invocations lists.
+ void two() {
+ invocations_.push_back(2);
+ }
+
+ /// @brief A callback that adds the value 3 to invocations lists.
+ void three() {
+ invocations_.push_back(3);
+ }
+
+ /// @brief A callback that adds the value 4 to invocations lists.
+ void four() {
+ invocations_.push_back(4);
+ }
+
+ /// @brief A callback that throws @ref isc::Exception which is ignored.
+ void ignoredException() {
+ isc_throw(isc::Exception, "ignored");
+ }
+
+ /// @brief A callback that throws @ref isc::MultiThreadingInvalidOperation
+ /// which is propagated to the scope of the
+ /// @ref MultiThreadingCriticalSection constructor.
+ void observedException() {
+ isc_throw(isc::MultiThreadingInvalidOperation, "observed");
+ }
+
+ /// @brief Indicates whether or not the DHCP thread pool is running.
+ ///
+ /// @return True if the pool is running, false otherwise.
+ bool isThreadPoolRunning() {
+ return (MultiThreadingMgr::instance().getThreadPool().size());
+ }
+
+ /// @brief Checks callback invocations over a series of nested
+ /// CriticalSections.
+ ///
+ /// @param entries A vector of the invocation values that should
+ /// be present after entry into the outermost CriticalSection. The
+ /// expected values should be in the order the callbacks were added
+ /// to the MultiThreadingMgr's list of callbacks.
+ /// @param exits A vector of the invocation values that should
+ /// be present after exiting the outermost CriticalSection. The
+ /// expected values should be in the order the callbacks were added
+ /// to the MultiThreadingMgr's list of callbacks.
+ /// @param should_throw The flag indicating if the CriticalSection should
+ /// throw, simulating a dead-lock scenario when a processing thread tries
+ /// to stop the thread pool.
+ void runCriticalSections(std::vector<int> entries, std::vector<int>exits,
+ bool should_throw = false) {
+ // Pool must be running.
+ ASSERT_TRUE(isThreadPoolRunning());
+
+ // Clear the invocations list.
+ invocations_.clear();
+
+ // Use scope to create nested CriticalSections.
+ if (!should_throw) {
+ // Enter a critical section.
+ MultiThreadingCriticalSection cs;
+
+ // Thread pool should be stopped.
+ ASSERT_FALSE(isThreadPoolRunning());
+
+ if (entries.size()) {
+ // We expect entry invocations.
+ ASSERT_EQ(invocations_.size(), entries.size());
+ ASSERT_EQ(invocations_, entries);
+ } else {
+ // We do not expect entry invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+
+ // Clear the invocations list.
+ invocations_.clear();
+
+ {
+ // Enter another CriticalSection.
+ MultiThreadingCriticalSection inner_cs;
+
+ // Thread pool should still be stopped.
+ ASSERT_FALSE(isThreadPoolRunning());
+
+ // We should not have had any callback invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+
+ // After exiting inner section, the thread pool should
+ // still be stopped.
+ ASSERT_FALSE(isThreadPoolRunning());
+
+ // We should not have had more callback invocations.
+ ASSERT_FALSE(invocations_.size());
+ } else {
+ ASSERT_THROW(MultiThreadingCriticalSection cs, MultiThreadingInvalidOperation);
+
+ if (entries.size()) {
+ // We expect entry invocations.
+ ASSERT_EQ(invocations_.size(), entries.size());
+ ASSERT_EQ(invocations_, entries);
+ } else {
+ // We do not expect entry invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+
+ // Clear the invocations list.
+ invocations_.clear();
+ }
+
+ // After exiting the outer section, the thread pool should
+ // match the thread count.
+ ASSERT_TRUE(isThreadPoolRunning());
+
+ if (exits.size()) {
+ // We expect exit invocations.
+ ASSERT_EQ(invocations_, exits);
+ } else {
+ // We do not expect exit invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+ }
+
+ /// @brief A list of values set by callback invocations.
+ std::vector<int> invocations_;
+};
+
+/// @brief Verifies critical section callback maintenance:
+/// catch invalid pairs, add pairs, remove pairs.
+TEST_F(CriticalSectionCallbackTest, addAndRemove) {
+ auto& mgr = MultiThreadingMgr::instance();
+
+ // Cannot add with a blank name.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("", [](){}, [](){}, [](){}),
+ BadValue, "CSCallbackSetList - name cannot be empty");
+
+ // Cannot add with an empty check callback.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", nullptr, [](){}, [](){}),
+ BadValue, "CSCallbackSetList - check callback for bad cannot be empty");
+
+ // Cannot add with an empty exit callback.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", [](){}, nullptr, [](){}),
+ BadValue, "CSCallbackSetList - entry callback for bad cannot be empty");
+
+ // Cannot add with an empty exit callback.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", [](){}, [](){}, nullptr),
+ BadValue, "CSCallbackSetList - exit callback for bad cannot be empty");
+
+ // Should be able to add foo.
+ ASSERT_NO_THROW_LOG(mgr.addCriticalSectionCallbacks("foo", [](){}, [](){}, [](){}));
+
+ // Should not be able to add foo twice.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("foo", [](){}, [](){}, [](){}),
+ BadValue, "CSCallbackSetList - callbacks for foo already exist");
+
+ // Should be able to add bar.
+ ASSERT_NO_THROW_LOG(mgr.addCriticalSectionCallbacks("bar", [](){}, [](){}, [](){}));
+
+ // Should be able to remove foo.
+ ASSERT_NO_THROW_LOG(mgr.removeCriticalSectionCallbacks("foo"));
+
+ // Should be able to remove foo twice without issue.
+ ASSERT_NO_THROW_LOG(mgr.removeCriticalSectionCallbacks("foo"));
+
+ // Should be able to remove all without issue.
+ ASSERT_NO_THROW_LOG(mgr.removeAllCriticalSectionCallbacks());
+}
+
+/// @brief Verifies that the critical section callbacks work.
+TEST_F(CriticalSectionCallbackTest, invocations) {
+ // get the thread pool instance
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+
+ // Add two sets of CriticalSection call backs.
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("oneAndTwo",
+ std::bind(&CriticalSectionCallbackTest::ignoredException, this),
+ std::bind(&CriticalSectionCallbackTest::one, this),
+ std::bind(&CriticalSectionCallbackTest::two, this));
+
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("threeAndFour",
+ std::bind(&CriticalSectionCallbackTest::ignoredException, this),
+ std::bind(&CriticalSectionCallbackTest::three, this),
+ std::bind(&CriticalSectionCallbackTest::four, this));
+
+ // Apply multi-threading configuration with 16 threads and queue size 256.
+ MultiThreadingMgr::instance().apply(true, 16, 256);
+
+ // Make three passes over nested CriticalSections to ensure
+ // callbacks execute at the appropriate times and we can do
+ // so repeatedly.
+ for (int i = 0; i < 3; ++i) {
+ runCriticalSections({1 ,3}, {4, 2});
+ }
+
+ // Now remove the first set of callbacks.
+ MultiThreadingMgr::instance().removeCriticalSectionCallbacks("oneAndTwo");
+
+ // Retest CriticalSections.
+ runCriticalSections({3}, {4});
+
+ // Now remove the remaining callbacks.
+ MultiThreadingMgr::instance().removeAllCriticalSectionCallbacks();
+
+ // Retest CriticalSections.
+ runCriticalSections({}, {});
+}
+
+/// @brief Verifies that the critical section callbacks work.
+TEST_F(CriticalSectionCallbackTest, invocationsWithExceptions) {
+ // get the thread pool instance
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+
+ // Apply multi-threading configuration with 16 threads and queue size 256.
+ MultiThreadingMgr::instance().apply(true, 16, 256);
+
+ // Add two sets of CriticalSection call backs.
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("observed",
+ std::bind(&CriticalSectionCallbackTest::observedException, this),
+ std::bind(&CriticalSectionCallbackTest::one, this),
+ std::bind(&CriticalSectionCallbackTest::two, this));
+
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("ignored",
+ std::bind(&CriticalSectionCallbackTest::ignoredException, this),
+ std::bind(&CriticalSectionCallbackTest::three, this),
+ std::bind(&CriticalSectionCallbackTest::four, this));
+
+ // Make three passes over nested CriticalSections to ensure
+ // callbacks execute at the appropriate times and we can do
+ // so repeatedly.
+ for (int i = 0; i < 3; ++i) {
+ runCriticalSections({}, {}, true);
+ }
+
+ // Now remove the first set of callbacks.
+ MultiThreadingMgr::instance().removeCriticalSectionCallbacks("observed");
+
+ // Retest CriticalSections.
+ runCriticalSections({3}, {4});
+
+ // Now remove the remaining callbacks.
+ MultiThreadingMgr::instance().removeAllCriticalSectionCallbacks();
+
+ // Retest CriticalSections.
+ runCriticalSections({}, {});
+}
diff --git a/src/lib/util/tests/optional_unittest.cc b/src/lib/util/tests/optional_unittest.cc
new file mode 100644
index 0000000..71830ab
--- /dev/null
+++ b/src/lib/util/tests/optional_unittest.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <util/optional.h>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc::util;
+
+// This test checks that the constructors work correctly.
+TEST(OptionalTest, constructor) {
+ // Explicitly set a value via constructor. The value becomes
+ // specified.
+ Optional<int> value1(10);
+ EXPECT_EQ(10, value1.get());
+ EXPECT_FALSE(value1.unspecified());
+
+ // Do not set a value in a constructor. The value should be
+ // unspecified.
+ Optional<int> value2;
+ EXPECT_EQ(0, value2.get());
+ EXPECT_TRUE(value2.unspecified());
+
+ // Use the non-default value for second parameter.
+ Optional<bool> value3(true, true);
+ EXPECT_TRUE(value3.get());
+ EXPECT_TRUE(value3.unspecified());
+}
+
+// This test checks if the constructors for a string value
+// work correctly.
+TEST(OptionalTest, constructorString) {
+ Optional<std::string> value1("foo");
+ EXPECT_EQ("foo", value1.get());
+ EXPECT_FALSE(value1.unspecified());
+
+ Optional<std::string> value2;
+ EXPECT_TRUE(value2.get().empty());
+ EXPECT_TRUE(value2.unspecified());
+}
+
+// This test checks if the assignment operator assigning an actual
+// value to the optional value works as expected.
+TEST(OptionalTest, assignValue) {
+ Optional<int> value(10, true);
+ EXPECT_EQ(10, value.get());
+ EXPECT_TRUE(value.unspecified());
+
+ // Assign a new value.
+ value = 111;
+ EXPECT_EQ(111, value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ // Assign another value.
+ value = 1000;
+ EXPECT_EQ(1000, value.get());
+ EXPECT_FALSE(value.unspecified());
+}
+
+// This test checks if the assignment operator assigning an actual
+// string value to the optional value works as expected.
+TEST(OptionalTest, assignStringValue) {
+ Optional<std::string> value("foo");
+ EXPECT_EQ("foo", value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ value = "bar";
+ EXPECT_EQ("bar", value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ value = "foobar";
+ EXPECT_EQ("foobar", value.get());
+ EXPECT_FALSE(value.unspecified());
+}
+
+// This test checks that it is possible to modify the flag that indicates
+// if the value is specified or unspecified.
+TEST(OptionalTest, modifyUnspecified) {
+ Optional<int> value;
+ EXPECT_TRUE(value.unspecified());
+
+ value.unspecified(false);
+ EXPECT_FALSE(value.unspecified());
+
+ value.unspecified(true);
+ EXPECT_TRUE(value.unspecified());
+}
+
+// This test checks if the type case operator returns correct value.
+TEST(OptionalTest, typeCastOperator) {
+ Optional<int> value(-10);
+ EXPECT_EQ(-10, value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ int actual = value;
+ EXPECT_EQ(-10, actual);
+}
+
+// This test checks if the type case operator returns correct string
+// value.
+TEST(OptionalTest, stringCastOperator) {
+ Optional<std::string> value("xyz");
+ EXPECT_EQ("xyz", value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ std::string actual = value;
+ EXPECT_EQ("xyz", actual);
+}
+
+// This test checks that the equality operators work as expected.
+TEST(OptionalTest, equality) {
+ int exp_value = 1234;
+ Optional<int> value(1234);
+ EXPECT_TRUE(value == exp_value);
+ EXPECT_FALSE(value != exp_value);
+}
+
+// This test checks that the equality operators for strings work as
+// expected.
+TEST(OptionalTest, stringEquality) {
+ const char* exp_value = "foo";
+ Optional<std::string> value("foo");
+ EXPECT_TRUE(value == exp_value);
+ EXPECT_FALSE(value != exp_value);
+}
+
+// This test checks that an exception is thrown when calling an empty()
+// method on non-string optional value.
+TEST(OptionalTest, empty) {
+ Optional<int> value(10);
+ EXPECT_THROW(value.empty(), isc::InvalidOperation);
+}
+
+// This test checks that no exception is thrown when calling an empty()
+// method on string optional value and that it returns an expected
+// boolean value.
+TEST(OptionalTest, stringEmpty) {
+ Optional<std::string> value("foo");
+ bool is_empty = true;
+ ASSERT_NO_THROW(is_empty = value.empty());
+ EXPECT_FALSE(is_empty);
+
+ value = "";
+ ASSERT_NO_THROW(is_empty = value.empty());
+ EXPECT_TRUE(is_empty);
+}
+
+// Checks that the valueOr function works correctly.
+TEST(OptionalTest, valueOr) {
+ Optional<std::string> optional("foo");
+ EXPECT_EQ(optional.valueOr("bar"), "foo");
+
+ Optional<std::string> unspecified_optional;
+ EXPECT_EQ(unspecified_optional.valueOr("bar"), "bar");
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/pid_file_unittest.cc b/src/lib/util/tests/pid_file_unittest.cc
new file mode 100644
index 0000000..5f00d72
--- /dev/null
+++ b/src/lib/util/tests/pid_file_unittest.cc
@@ -0,0 +1,206 @@
+// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/pid_file.h>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <signal.h>
+#include <stdint.h>
+
+namespace {
+using namespace isc::util;
+
+// Filenames used for testing.
+const char* TESTNAME = "pid_file.test";
+
+class PIDFileTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ PIDFileTest() = default;
+
+ /// @brief Destructor
+ virtual ~PIDFileTest() = default;
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Generate a random number for use as a PID
+ ///
+ /// @param start - the start of the range we want the PID in
+ /// @param range - the size of the range for our PID
+ ///
+ /// @return returns a random value between start and start + range
+ int randomizePID(const uint32_t start, const uint32_t range) {
+ int pid;
+
+ for (pid = (random() % range) + start;
+ kill(pid, 0) == 0;
+ ++pid)
+ ;
+
+ return (pid);
+ }
+
+protected:
+ /// @brief Removes any old test files before the test
+ virtual void SetUp() {
+ removeTestFile();
+ }
+
+ /// @brief Removes any remaining test files after the test
+ virtual void TearDown() {
+ removeTestFile();
+ }
+
+private:
+ /// @brief Removes any remaining test files
+ void removeTestFile() const {
+ static_cast<void>(remove(absolutePath(TESTNAME).c_str()));
+ }
+
+};
+
+std::string
+PIDFileTest::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << TEST_DATA_BUILDDIR << "/" << filename;
+
+ return (s.str());
+}
+
+/// @brief Test file writing and deletion. Start by removing
+/// any leftover file. Then write a known PID to the file and
+/// attempt to read the file and verify the PID. Next write
+/// a second and verify a second PID to verify that an existing
+/// file is properly overwritten.
+
+TEST_F(PIDFileTest, writeAndDelete) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+ std::ifstream fs;
+ int pid(0);
+
+ // Write a known process id
+ pid_file.write(10);
+
+ // Read the file and compare the pid
+ fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+ fs >> pid;
+ EXPECT_TRUE(fs.good());
+ EXPECT_EQ(pid, 10);
+ fs.close();
+
+ // Write a second known process id
+ pid_file.write(20);
+
+ // And compare the second pid
+ fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+ fs >> pid;
+ EXPECT_TRUE(fs.good());
+ EXPECT_EQ(pid, 20);
+ fs.close();
+
+ // Delete the file
+ pid_file.deleteFile();
+
+ // And verify that it's gone
+ fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+ EXPECT_FALSE(fs.good());
+ fs.close();
+}
+
+/// @brief Test checking a PID. Write the PID of the current
+/// process to the PID file then verify that check indicates
+/// the process is running.
+TEST_F(PIDFileTest, pidInUse) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+
+ // Write the current PID
+ pid_file.write();
+
+ // Check if we think the process is running
+ EXPECT_EQ(getpid(), pid_file.check());
+}
+
+/// @brief Test checking a PID. Write a PID that isn't in use
+/// to the PID file and verify that check indicates the process
+/// isn't running. The PID may get used between when we select it
+/// and write the file and when we check it. To minimize false
+/// errors if the first call to check fails we try again with a
+/// different range of values and only if both attempts fail do
+/// we declare the test to have failed.
+TEST_F(PIDFileTest, pidNotInUse) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+ int pid;
+
+ // get a pid between 10000 and 20000
+ pid = randomizePID(10000, 10000);
+
+ // write it
+ pid_file.write(pid);
+
+ // Check to see if we think the process is running
+ if (pid_file.check() == 0) {
+ return;
+ }
+
+ // get a pid between 40000 and 50000
+ pid = randomizePID(10000, 40000);
+
+ // write it
+ pid_file.write(pid);
+
+ // Check to see if we think the process is running
+ EXPECT_EQ(0, pid_file.check());
+}
+
+/// @brief Test checking a PID. Write garbage to the PID file
+/// and verify that check throws an error. In this situation
+/// the caller should probably log an error and may decide to
+/// continue or not depending on the requirements.
+TEST_F(PIDFileTest, pidGarbage) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+ std::ofstream fs;
+
+ // Open the file and write garbage to it
+ fs.open(absolutePath(TESTNAME).c_str(), std::ofstream::out);
+ fs << "text" << std::endl;
+ fs.close();
+
+ // Run the check, we expect to get an exception
+ EXPECT_THROW(pid_file.check(), PIDCantReadPID);
+}
+
+/// @brief Test failing to write a file.
+TEST_F(PIDFileTest, pidWriteFail) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+
+ // Create the test file and change it's permission bits
+ // so we can't write to it.
+ pid_file.write(10);
+ chmod(absolutePath(TESTNAME).c_str(), S_IRUSR);
+
+ // Now try a write to the file, expecting an exception
+ EXPECT_THROW(pid_file.write(10), PIDFileError);
+
+ // Don't forget to restore the write right for the next test
+ chmod(absolutePath(TESTNAME).c_str(), S_IRUSR | S_IWUSR);
+}
+
+/// @brief Test deleting a file that doesn't exist
+TEST_F(PIDFileTest, noDeleteFile) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+
+ // Delete a file we haven't created
+ pid_file.deleteFile();
+}
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/range_utilities_unittest.cc b/src/lib/util/tests/range_utilities_unittest.cc
new file mode 100644
index 0000000..ce94a38
--- /dev/null
+++ b/src/lib/util/tests/range_utilities_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <gtest/gtest.h>
+#include <vector>
+
+#include <util/range_utilities.h>
+
+using namespace std;
+using namespace isc::util;
+
+TEST(RangeUtilitiesTest, isZero) {
+
+ vector<uint8_t> vec(32,0);
+
+ EXPECT_TRUE(isRangeZero(vec.begin(), vec.end()));
+
+ EXPECT_TRUE(isRangeZero(vec.begin(), vec.begin()+1));
+
+ vec[5] = 1;
+ EXPECT_TRUE(isRangeZero(vec.begin(), vec.begin()+5));
+ EXPECT_FALSE(isRangeZero(vec.begin(), vec.begin()+6));
+}
+
+TEST(RangeUtilitiesTest, randomFill) {
+
+ srandom(time(NULL));
+
+ vector<uint8_t> vec1(16,0);
+ vector<uint8_t> vec2(16,0);
+
+ // Testing if returned value is actually random is extraordinary difficult.
+ // Let's just generate bunch of vectors and see if we get the same
+ // value. If we manage to do that in 100 tries, pseudo-random generator
+ // really sucks.
+ fillRandom(vec1.begin(), vec1.end());
+ for (int i=0; i<100; i++) {
+ fillRandom(vec2.begin(), vec2.end());
+ if (vec1 == vec2)
+ FAIL();
+ }
+
+}
diff --git a/src/lib/util/tests/readwrite_mutex_unittest.cc b/src/lib/util/tests/readwrite_mutex_unittest.cc
new file mode 100644
index 0000000..6b4af5f
--- /dev/null
+++ b/src/lib/util/tests/readwrite_mutex_unittest.cc
@@ -0,0 +1,470 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/readwrite_mutex.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/make_shared.hpp>
+
+#include <chrono>
+#include <iostream>
+#include <thread>
+#include <unistd.h>
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief Test Fixture for testing read-write mutexes.
+///
+/// Each not basic test follows the same schema:
+/// @code
+/// main thread work thread
+/// <- started
+/// work ->
+/// enter guard
+/// <- done
+/// terminate ->
+/// return
+/// join
+/// @endcode
+class ReadWriteMutexTest : public ::testing::Test {
+public:
+
+ /// @brief Read-write mutex.
+ ReadWriteMutex rw_mutex_;
+
+ /// @brief Synchronization objects for work threads.
+ struct Sync {
+ bool started = false;
+ mutex started_mtx;
+ condition_variable started_cv;
+ bool work = false;
+ mutex work_mtx;
+ condition_variable work_cv;
+ bool done = false;
+ mutex done_mtx;
+ condition_variable done_cv;
+ bool terminate = false;
+ mutex terminate_mtx;
+ condition_variable terminate_cv;
+ } syncr_, syncw_;
+
+ /// @brief Body of the reader.
+ ///
+ /// @param rw_mutex The read-write mutex.
+ /// @param syncr The reader synchronization object.
+ void reader(ReadWriteMutex& rw_mutex, Sync& syncr) {
+ // Take mutex to wait for main thread signals.
+ unique_lock<mutex> terminate_lock(syncr.terminate_mtx);
+
+ // Signal the thread started.
+ {
+ lock_guard<mutex> lock(syncr.started_mtx);
+ syncr.started = true;
+ }
+
+ // Wait for work.
+ {
+ unique_lock<mutex> work_lock(syncr.work_mtx);
+ // When this thread starts waiting, the main thread is resumed.
+ syncr.started_cv.notify_one();
+ syncr.work_cv.wait(work_lock, [&](){ return syncr.work; });
+ }
+
+ {
+ // Enter a read lock guard.
+ ReadLockGuard rwlock(rw_mutex);
+
+ // Signal the thread holds the guard.
+ {
+ lock_guard<mutex> done_lock(syncr.done_mtx);
+ syncr.done = true;
+ }
+ syncr.done_cv.notify_one();
+ }
+
+ // Wait to terminate.
+ syncr.terminate_cv.wait(terminate_lock, [&](){ return syncr.terminate; });
+ }
+
+ /// @brief Body of the writer.
+ ///
+ /// @param rw_mutex The read-write mutex.
+ /// @param syncw The writer synchronization object.
+ void writer(ReadWriteMutex& rw_mutex, Sync& syncw) {
+ // Take mutex to wait for main thread signals.
+ unique_lock<mutex> terminate_lock(syncw.terminate_mtx);
+
+ // Signal the thread started.
+ {
+ lock_guard<mutex> lock(syncw.started_mtx);
+ syncw.started = true;
+ }
+
+ // Wait for work.
+ {
+ unique_lock<mutex> work_lock(syncw.work_mtx);
+ // When this thread starts waiting, the main thread is resumed.
+ syncw.started_cv.notify_one();
+ syncw.work_cv.wait(work_lock, [&](){ return syncw.work; });
+ }
+
+ {
+ // Enter a write lock guard.
+ WriteLockGuard rwlock(rw_mutex);
+
+ // Signal the thread holds the guard.
+ {
+ lock_guard<mutex> done_lock(syncw.done_mtx);
+ syncw.done = true;
+ }
+ syncw.done_cv.notify_one();
+ }
+
+ // Wait to terminate.
+ syncw.terminate_cv.wait(terminate_lock, [&](){ return syncw.terminate; });
+ }
+};
+
+// Verify basic read lock guard.
+TEST_F(ReadWriteMutexTest, basicRead) {
+ ReadLockGuard lock(rw_mutex_);
+}
+
+// Verify basic write lock guard.
+TEST_F(ReadWriteMutexTest, basicWrite) {
+ WriteLockGuard lock(rw_mutex_);
+}
+
+// Verify read lock guard using a thread.
+TEST_F(ReadWriteMutexTest, read) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncr_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); });
+
+ // Wait work thread to start.
+ syncr_.started_cv.wait(started_lock, [this](){ return syncr_.started; });
+
+ unique_lock<mutex> done_lock(syncr_.done_mtx);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncr_.work_mtx);
+ syncr_.work = true;
+ }
+ syncr_.work_cv.notify_one();
+
+ // Wait thread to hold the read lock.
+ syncr_.done_cv.wait(done_lock, [this](){ return syncr_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncr_.terminate_mtx);
+ syncr_.terminate = true;
+ }
+ syncr_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify write lock guard using a thread.
+TEST_F(ReadWriteMutexTest, write) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncw_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
+
+ // Wait work thread to start.
+ syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; });
+
+ unique_lock<mutex> done_lock(syncw_.done_mtx);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncw_.work_mtx);
+ syncw_.work = true;
+ }
+ syncw_.work_cv.notify_one();
+
+ // Wait thread to hold the write lock.
+ syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
+ syncw_.terminate = true;
+ }
+ syncw_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify read lock guard can be acquired by multiple threads.
+TEST_F(ReadWriteMutexTest, readRead) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncr_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); });
+
+ // Enter a read lock guard.
+ ReadLockGuard rwlock(rw_mutex_);
+
+ // Wait work thread to start.
+ syncr_.started_cv.wait(started_lock, [this](){ return syncr_.started; });
+
+ unique_lock<mutex> done_lock(syncr_.done_mtx);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncr_.work_mtx);
+ syncr_.work = true;
+ }
+ syncr_.work_cv.notify_one();
+
+ // Wait thread to hold the read lock.
+ syncr_.done_cv.wait(done_lock, [this](){ return syncr_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncr_.terminate_mtx);
+ syncr_.terminate = true;
+ }
+ syncr_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify write lock guard is exclusive of a reader.
+TEST_F(ReadWriteMutexTest, readWrite) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncw_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
+
+ // Wait work thread to start.
+ syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; });
+
+ unique_lock<mutex> done_lock(syncw_.done_mtx);
+
+ {
+ // Enter a read lock guard.
+ ReadLockGuard rwlock(rw_mutex_);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncw_.work_mtx);
+ syncw_.work = true;
+ }
+ syncw_.work_cv.notify_one();
+
+ // Verify the work thread is waiting for the write lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncw_.done_cv.wait_for(done_lock, chrono::seconds(1), [this](){ return syncw_.done; });
+
+ EXPECT_FALSE(syncw_.done);
+ EXPECT_FALSE(ret);
+
+ // Exiting the read lock guard.
+ }
+
+ // Wait thread to hold the write lock.
+ syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
+ syncw_.terminate = true;
+ }
+ syncw_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify write lock guard is exclusive of a writer.
+TEST_F(ReadWriteMutexTest, writeWrite) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncw_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
+
+ // Wait work thread to start.
+ syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; });
+
+ unique_lock<mutex> done_lock(syncw_.done_mtx);
+
+ {
+ // Enter a write lock guard.
+ WriteLockGuard rwlock(rw_mutex_);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncw_.work_mtx);
+ syncw_.work = true;
+ }
+ syncw_.work_cv.notify_one();
+
+ // Verify the work thread is waiting for the write lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncw_.done_cv.wait_for(done_lock, chrono::seconds(1), [this](){ return syncw_.done; });
+
+ EXPECT_FALSE(syncw_.done);
+ EXPECT_FALSE(ret);
+
+ // Exiting the write lock guard.
+ }
+
+ // Wait thread to hold the write lock.
+ syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
+ syncw_.terminate = true;
+ }
+ syncw_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify that a writer has the preference.
+TEST_F(ReadWriteMutexTest, readWriteRead) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> threadw;
+ {
+ unique_lock<mutex> startedw_lock(syncw_.started_mtx);
+
+ // First thread is a writer.
+ threadw = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
+
+ // Wait work thread to start.
+ syncw_.started_cv.wait(startedw_lock, [this](){ return syncw_.started; });
+ }
+
+ boost::shared_ptr<std::thread> threadr;
+ {
+ unique_lock<mutex> startedr_lock(syncr_.started_mtx);
+
+ // Second thread is a reader.
+ threadr = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); });
+
+ // Wait work thread to start.
+ syncr_.started_cv.wait(startedr_lock, [this](){ return syncr_.started; });
+ }
+
+ {
+ unique_lock<mutex> donew_lock(syncw_.done_mtx);
+ {
+ // Enter a read lock guard.
+ ReadLockGuard rwlock(rw_mutex_);
+
+ // Signal the writer thread to work.
+ {
+ lock_guard<mutex> work_lock(syncw_.work_mtx);
+ syncw_.work = true;
+ }
+ syncw_.work_cv.notify_one();
+
+ // Verify the writer thread is waiting for the write lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncw_.done_cv.wait_for(donew_lock, chrono::seconds(1), [this](){ return syncw_.done; });
+
+ EXPECT_FALSE(syncw_.done);
+ EXPECT_FALSE(ret);
+
+ {
+ unique_lock<mutex> doner_lock(syncr_.done_mtx);
+
+ // Signal the reader thread to work.
+ {
+ lock_guard<mutex> work_lock(syncr_.work_mtx);
+ syncr_.work = true;
+ }
+ syncr_.work_cv.notify_one();
+
+ // Verify the reader thread is waiting for the read lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncr_.done_cv.wait_for(doner_lock, chrono::seconds(1), [this](){ return syncr_.done; });
+
+ EXPECT_FALSE(syncr_.done);
+ EXPECT_FALSE(ret);
+ }
+ // Exiting the read lock guard.
+ }
+
+ {
+ unique_lock<mutex> doner_lock(syncr_.done_mtx);
+ // Verify the reader thread is still waiting for the read lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncr_.done_cv.wait_for(doner_lock, chrono::seconds(1), [this](){ return syncr_.done; });
+
+ EXPECT_FALSE(syncr_.done);
+ EXPECT_FALSE(ret);
+ }
+
+ // Wait writer thread to hold the write lock.
+ syncw_.done_cv.wait(donew_lock, [this](){ return syncw_.done; });
+ }
+
+ {
+ unique_lock<mutex> doner_lock(syncr_.done_mtx);
+ // Wait reader thread to hold the read lock.
+ syncr_.done_cv.wait(doner_lock, [this](){ return syncr_.done; });
+ }
+
+ // Signal the writer thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
+ syncw_.terminate = true;
+ }
+ syncw_.terminate_cv.notify_one();
+
+ // Join the writer thread.
+ threadw->join();
+
+ // Signal the reader thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncr_.terminate_mtx);
+ syncr_.terminate = true;
+ }
+ syncr_.terminate_cv.notify_one();
+
+ // Join the reader thread.
+ threadr->join();
+}
+
+}
diff --git a/src/lib/util/tests/run_unittests.cc b/src/lib/util/tests/run_unittests.cc
new file mode 100644
index 0000000..ef2a372
--- /dev/null
+++ b/src/lib/util/tests/run_unittests.cc
@@ -0,0 +1,18 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+#include <stdlib.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/util/tests/staged_value_unittest.cc b/src/lib/util/tests/staged_value_unittest.cc
new file mode 100644
index 0000000..bcfc677
--- /dev/null
+++ b/src/lib/util/tests/staged_value_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <util/staged_value.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc::util;
+
+// This test verifies that the value can be assigned and committed.
+TEST(StagedValueTest, assignAndCommit) {
+ // Initially the value should be set to a default
+ StagedValue<int> value;
+ ASSERT_EQ(0, value.getValue());
+
+ // Set the new value without committing it and make sure it
+ // can be retrieved.
+ value.setValue(4);
+ ASSERT_EQ(4, value.getValue());
+
+ // Commit the value and make sure it still can be retrieved.
+ value.commit();
+ ASSERT_EQ(4, value.getValue());
+
+ // Set new value and retrieve it.
+ value.setValue(10);
+ ASSERT_EQ(10, value.getValue());
+
+ // Do it again and commit it.
+ value.setValue(20);
+ ASSERT_EQ(20, value.getValue());
+ value.commit();
+ EXPECT_EQ(20, value.getValue());
+}
+
+// This test verifies that the value can be reverted if it hasn't been
+// committed.
+TEST(StagedValueTest, revert) {
+ // Set the value and commit.
+ StagedValue<int> value;
+ value.setValue(123);
+ value.commit();
+
+ // Set new value and do not commit.
+ value.setValue(500);
+ // The new value should be the one returned.
+ ASSERT_EQ(500, value.getValue());
+ // But, reverting gets us back to original value.
+ value.revert();
+ EXPECT_EQ(123, value.getValue());
+ // Reverting again doesn't have any effect.
+ value.revert();
+ EXPECT_EQ(123, value.getValue());
+}
+
+// This test verifies that the value can be restored to an original one.
+TEST(StagedValueTest, reset) {
+ // Set the new value and commit.
+ StagedValue<int> value;
+ value.setValue(123);
+ value.commit();
+
+ // Override the value but do not commit.
+ value.setValue(500);
+
+ // Resetting should take us back to default value.
+ value.reset();
+ EXPECT_EQ(0, value.getValue());
+ value.revert();
+ EXPECT_EQ(0, value.getValue());
+}
+
+// This test verifies that second commit doesn't modify a value.
+TEST(StagedValueTest, commit) {
+ // Set the value and commit.
+ StagedValue<int> value;
+ value.setValue(123);
+ value.commit();
+
+ // Second commit should have no effect.
+ value.commit();
+ EXPECT_EQ(123, value.getValue());
+}
+
+// This test checks that type conversion operator works correctly.
+TEST(StagedValueTest, conversionOperator) {
+ StagedValue<int> value;
+ value.setValue(244);
+ EXPECT_EQ(244, value);
+}
+
+// This test checks that the assignment operator works correctly.
+TEST(StagedValueTest, assignmentOperator) {
+ StagedValue<int> value;
+ value = 111;
+ EXPECT_EQ(111, value);
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/state_model_unittest.cc b/src/lib/util/tests/state_model_unittest.cc
new file mode 100644
index 0000000..eaaba73
--- /dev/null
+++ b/src/lib/util/tests/state_model_unittest.cc
@@ -0,0 +1,916 @@
+// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/state_model.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test derivation of StateModel for exercising state model mechanics.
+///
+/// This class facilitates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines. It implements a very
+/// rudimentary state model, sufficient to test the state model mechanics
+/// supplied by the base class.
+class StateModelTest : public StateModel, public testing::Test {
+public:
+
+ ///@brief StateModelTest states
+ ///@brief Fake state used for handler mapping tests.
+ static const int DUMMY_ST = SM_DERIVED_STATE_MIN + 1;
+
+ ///@brief Starting state for the test state model.
+ static const int READY_ST = SM_DERIVED_STATE_MIN + 2;
+
+ ///@brief State which simulates doing asynchronous work.
+ static const int DO_WORK_ST = SM_DERIVED_STATE_MIN + 3;
+
+ ///@brief State which finishes off processing.
+ static const int DONE_ST = SM_DERIVED_STATE_MIN + 4;
+
+ ///@brief State in which model is always paused.
+ static const int PAUSE_ALWAYS_ST = SM_DERIVED_STATE_MIN + 5;
+
+ ///@brief State in which model is paused at most once.
+ static const int PAUSE_ONCE_ST = SM_DERIVED_STATE_MIN + 6;
+
+ // StateModelTest events
+ ///@brief Event used to trigger initiation of asynchronous work.
+ static const int WORK_START_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ ///@brief Event issued when the asynchronous work "completes".
+ static const int WORK_DONE_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ ///@brief Event issued when all the work is done.
+ static const int ALL_DONE_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ ///@brief Event used to trigger an attempt to transition to bad state
+ static const int FORCE_UNDEFINED_ST_EVT = SM_DERIVED_EVENT_MIN + 4;
+
+ ///@brief Event used to trigger an attempt to transition to bad state
+ static const int SIMULATE_ERROR_EVT = SM_DERIVED_EVENT_MIN + 5;
+
+ ///@brief Event used to indicate that state machine is unpaused.
+ static const int UNPAUSED_EVT = SM_DERIVED_EVENT_MIN + 6;
+
+ /// @brief Constructor
+ ///
+ /// Parameters match those needed by StateModel.
+ StateModelTest() : dummy_called_(false), work_completed_(false),
+ failure_explanation_("") {
+ }
+ /// @brief Destructor
+ virtual ~StateModelTest() {
+ }
+
+ /// @brief Fetches the value of the dummy called flag.
+ bool getDummyCalled() {
+ return (dummy_called_);
+ }
+
+ /// @brief StateHandler for fake state, DummyState.
+ ///
+ /// It simply sets the dummy called flag to indicate that this method
+ /// was invoked.
+ void dummyHandler() {
+ dummy_called_ = true;
+ }
+
+ /// @brief Returns the failure explanation string.
+ ///
+ /// This value is set only via onModelFailure and it stores whatever
+ /// explanation that method was passed.
+ const std::string& getFailureExplanation() {
+ return (failure_explanation_);
+ }
+
+ /// @brief Returns indication of whether or not the model succeeded.
+ ///
+ /// If true, this indicates that the test model executed correctly through
+ /// to completion. The flag is only by the DONE_ST handler.
+ bool getWorkCompleted() {
+ return (work_completed_);
+ }
+
+ /// @brief State handler for the READY_ST.
+ ///
+ /// Serves as the starting state handler, it consumes the
+ /// START_EVT "transitioning" to the state, DO_WORK_ST and
+ /// sets the next event to WORK_START_EVT.
+ void readyHandler() {
+ switch(getNextEvent()) {
+ case START_EVT:
+ transition(DO_WORK_ST, WORK_START_EVT);
+ break;
+ default:
+ // its bogus
+ isc_throw(StateModelError, "readyHandler:invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the DO_WORK_ST.
+ ///
+ /// Simulates a state that starts some form of asynchronous work.
+ /// When next event is WORK_START_EVT it sets the status to pending
+ /// and signals the state model must "wait" for an event by setting
+ /// next event to NOP_EVT.
+ ///
+ /// When next event is IO_COMPLETED_EVT, it transitions to the state,
+ /// DONE_ST, and sets the next event to WORK_DONE_EVT.
+ void doWorkHandler() {
+ switch(getNextEvent()) {
+ case WORK_START_EVT:
+ postNextEvent(NOP_EVT);
+ break;
+ case WORK_DONE_EVT:
+ work_completed_ = true;
+ transition(DONE_ST, ALL_DONE_EVT);
+ break;
+ case FORCE_UNDEFINED_ST_EVT:
+ transition(9999, ALL_DONE_EVT);
+ break;
+ case SIMULATE_ERROR_EVT:
+ throw std::logic_error("Simulated Unexpected Error");
+ break;
+ default:
+ // its bogus
+ isc_throw(StateModelError, "doWorkHandler:invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the DONE_ST.
+ ///
+ /// This is the last state in the model. Note that it sets the
+ /// status to completed and next event to NOP_EVT.
+ void doneWorkHandler() {
+ switch(getNextEvent()) {
+ case ALL_DONE_EVT:
+ endModel();
+ break;
+ default:
+ // its bogus
+ isc_throw(StateModelError, "doneWorkHandler:invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for PAUSE_ALWAYS_ST and PAUSE_ONCE_ST.
+ void pauseHandler() {
+ postNextEvent(NOP_EVT);
+ }
+
+ /// @brief Construct the event dictionary.
+ virtual void defineEvents() {
+ // Invoke the base call implementation first.
+ StateModel::defineEvents();
+
+ // Define our events.
+ defineEvent(WORK_START_EVT, "WORK_START_EVT");
+ defineEvent(WORK_DONE_EVT , "WORK_DONE_EVT");
+ defineEvent(ALL_DONE_EVT, "ALL_DONE_EVT");
+ defineEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT");
+ defineEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT");
+ defineEvent(UNPAUSED_EVT, "UNPAUSED_EVT");
+ }
+
+ /// @brief Verify the event dictionary.
+ virtual void verifyEvents() {
+ // Invoke the base call implementation first.
+ StateModel::verifyEvents();
+
+ // Verify our events.
+ getEvent(WORK_START_EVT);
+ getEvent(WORK_DONE_EVT);
+ getEvent(ALL_DONE_EVT);
+ getEvent(FORCE_UNDEFINED_ST_EVT);
+ getEvent(SIMULATE_ERROR_EVT);
+ getEvent(UNPAUSED_EVT);
+ }
+
+ /// @brief Construct the state dictionary.
+ virtual void defineStates() {
+ // Invoke the base call implementation first.
+ StateModel::defineStates();
+
+ // Define our states.
+ defineState(DUMMY_ST, "DUMMY_ST",
+ std::bind(&StateModelTest::dummyHandler, this));
+
+ defineState(READY_ST, "READY_ST",
+ std::bind(&StateModelTest::readyHandler, this));
+
+ defineState(DO_WORK_ST, "DO_WORK_ST",
+ std::bind(&StateModelTest::doWorkHandler, this));
+
+ defineState(DONE_ST, "DONE_ST",
+ std::bind(&StateModelTest::doneWorkHandler, this));
+
+ defineState(PAUSE_ALWAYS_ST, "PAUSE_ALWAYS_ST",
+ std::bind(&StateModelTest::pauseHandler, this),
+ STATE_PAUSE_ALWAYS);
+
+ defineState(PAUSE_ONCE_ST, "PAUSE_ONCE_ST",
+ std::bind(&StateModelTest::pauseHandler, this),
+ STATE_PAUSE_ONCE);
+ }
+
+ /// @brief Verify the state dictionary.
+ virtual void verifyStates() {
+ // Invoke the base call implementation first.
+ StateModel::verifyStates();
+
+ // Verify our states.
+ getStateInternal(DUMMY_ST);
+ getStateInternal(READY_ST);
+ getStateInternal(DO_WORK_ST);
+ getStateInternal(DONE_ST);
+ getStateInternal(PAUSE_ALWAYS_ST);
+ getStateInternal(PAUSE_ONCE_ST);
+ }
+
+ /// @brief Manually construct the event and state dictionaries.
+ /// This allows testing without running startModel.
+ void initDictionaries() {
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+ }
+
+ /// @brief Tests the event dictionary entry for the given event value.
+ bool checkEvent(const int value, const std::string& label) {
+ EventPtr event;
+ try {
+ event = getEvent(value);
+ EXPECT_TRUE(event);
+ EXPECT_EQ(value, event->getValue());
+ EXPECT_EQ(label, event->getLabel());
+ } catch (const std::exception& ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// @brief Tests the state dictionary entry for the given state value.
+ bool checkState(const int value, const std::string& label) {
+ EventPtr state;
+ try {
+ state = getState(value);
+ EXPECT_TRUE(state);
+ EXPECT_EQ(value, state->getValue());
+ EXPECT_EQ(label, state->getLabel());
+ } catch (const std::exception& ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /// @brief Handler called when the model suffers an execution error.
+ virtual void onModelFailure(const std::string& explanation) {
+ failure_explanation_ = explanation;
+ }
+
+ /// @brief Indicator of whether or not the DUMMY_ST handler has been called.
+ bool dummy_called_;
+
+ /// @brief Indicator of whether or not DONE_ST handler has been called.
+ bool work_completed_;
+
+ /// @brief Stores the failure explanation
+ std::string failure_explanation_;
+};
+
+// Declare them so gtest can see them.
+const int StateModelTest::DUMMY_ST;
+const int StateModelTest::READY_ST;
+const int StateModelTest::DO_WORK_ST;
+const int StateModelTest::DONE_ST;
+const int StateModelTest::WORK_START_EVT;
+const int StateModelTest::WORK_DONE_EVT;
+const int StateModelTest::ALL_DONE_EVT;
+const int StateModelTest::PAUSE_ALWAYS_ST;
+const int StateModelTest::PAUSE_ONCE_ST;
+
+/// @brief Checks the fundamentals of defining and retrieving events.
+TEST_F(StateModelTest, eventDefinition) {
+ // After construction, the event dictionary should be empty. Verify that
+ // getEvent will throw when event is not defined.
+ EXPECT_THROW(getEvent(NOP_EVT), StateModelError);
+
+ // Verify that we can add a handler to the map.
+ ASSERT_NO_THROW(defineEvent(NOP_EVT, "NOP_EVT"));
+
+ // Verify that we can find the event by value and its content is correct.
+ EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+
+ // Verify that we cannot add a duplicate.
+ ASSERT_THROW(defineEvent(NOP_EVT, "NOP_EVT"), StateModelError);
+
+ // Verify that we can still find the event.
+ EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+}
+
+/// @brief Tests event dictionary construction and verification.
+TEST_F(StateModelTest, eventDictionary) {
+ // After construction, the event dictionary should be empty.
+ // Make sure that verifyEvents() throws.
+ EXPECT_THROW(verifyEvents(), StateModelError);
+
+ // Construct the dictionary and verify it.
+ EXPECT_NO_THROW(defineEvents());
+ EXPECT_NO_THROW(verifyEvents());
+
+ // Verify base class events are defined.
+ EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+ EXPECT_TRUE(checkEvent(START_EVT, "START_EVT"));
+ EXPECT_TRUE(checkEvent(END_EVT, "END_EVT"));
+ EXPECT_TRUE(checkEvent(FAIL_EVT, "FAIL_EVT"));
+
+ // Verify stub class events are defined.
+ EXPECT_TRUE(checkEvent(WORK_START_EVT, "WORK_START_EVT"));
+ EXPECT_TRUE(checkEvent(WORK_DONE_EVT, "WORK_DONE_EVT"));
+ EXPECT_TRUE(checkEvent(ALL_DONE_EVT, "ALL_DONE_EVT"));
+ EXPECT_TRUE(checkEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT"));
+ EXPECT_TRUE(checkEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT"));
+
+ // Verify that undefined events are handled correctly.
+ EXPECT_THROW(getEvent(9999), StateModelError);
+ EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, getEventLabel(9999));
+}
+
+/// @brief General testing of event context accessors.
+/// Most if not all of these are also tested as a byproduct off larger tests.
+TEST_F(StateModelTest, eventContextAccessors) {
+ // Construct the event definitions, normally done by startModel.
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+
+ // Verify the post-construction values.
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call setEvent which will update both next event and last event.
+ EXPECT_NO_THROW(postNextEvent(START_EVT));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(START_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call setEvent again.
+ EXPECT_NO_THROW(postNextEvent(WORK_START_EVT));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(WORK_START_EVT, getNextEvent());
+ EXPECT_EQ(START_EVT, getLastEvent());
+
+ // Verify that posting an undefined event throws.
+ EXPECT_THROW(postNextEvent(9999), StateModelError);
+}
+
+/// @brief Tests the fundamental methods used for state handler mapping.
+/// Verifies the ability to search for and add entries in the state handler map.
+TEST_F(StateModelTest, stateDefinition) {
+ // After construction, the state dictionary should be empty. Verify that
+ // getState will throw when, state is not defined.
+ EXPECT_THROW(getState(READY_ST), StateModelError);
+
+ // Verify that we can add a state to the dictionary.
+ ASSERT_NO_THROW(defineState(READY_ST, "READY_ST",
+ std::bind(&StateModelTest::dummyHandler,
+ this)));
+
+ // Verify that we can find the state by its value.
+ StatePtr state;
+ EXPECT_NO_THROW(state = getState(READY_ST));
+ EXPECT_TRUE(state);
+
+ // Verify the state's value and label.
+ EXPECT_EQ(READY_ST, state->getValue());
+ EXPECT_EQ("READY_ST", state->getLabel());
+
+ // Now verify that retrieved state's handler executes the correct method.
+ // Make sure the dummy called flag is false prior to invocation.
+ EXPECT_FALSE(getDummyCalled());
+
+ // Invoke the state's handler.
+ EXPECT_NO_THROW(state->run());
+
+ // Verify the dummy called flag is now true.
+ EXPECT_TRUE(getDummyCalled());
+
+ // Verify that we cannot add a duplicate.
+ EXPECT_THROW(defineState(READY_ST, "READY_ST",
+ std::bind(&StateModelTest::readyHandler, this)),
+ StateModelError);
+
+ // Verify that we can still find the state.
+ EXPECT_NO_THROW(getState(READY_ST));
+}
+
+/// @brief Tests state dictionary initialization and validation.
+/// This tests the basic concept of state dictionary initialization and
+/// verification by manually invoking the methods normally called by startModel.
+TEST_F(StateModelTest, stateDictionary) {
+ // Verify that the map validation throws prior to the dictionary being
+ // initialized.
+ EXPECT_THROW(verifyStates(), StateModelError);
+
+ // Construct the dictionary and verify it.
+ ASSERT_NO_THROW(defineStates());
+ EXPECT_NO_THROW(verifyStates());
+
+ // Verify the base class states.
+ EXPECT_TRUE(checkState(NEW_ST, "NEW_ST"));
+ EXPECT_TRUE(checkState(END_ST, "END_ST"));
+
+ // Verify stub class states.
+ EXPECT_TRUE(checkState(DUMMY_ST, "DUMMY_ST"));
+ EXPECT_TRUE(checkState(READY_ST, "READY_ST"));
+ EXPECT_TRUE(checkState(DO_WORK_ST, "DO_WORK_ST"));
+ EXPECT_TRUE(checkState(DONE_ST, "DONE_ST"));
+
+ // Verify that undefined states are handled correctly.
+ EXPECT_THROW(getState(9999), StateModelError);
+ EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, getStateLabel(9999));
+}
+
+/// @brief General testing of state context accessors.
+/// Most if not all of these are also tested as a byproduct off larger tests.
+TEST_F(StateModelTest, stateContextAccessors) {
+ // setState will throw unless we initialize the handler map.
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+
+ // Verify post-construction state values.
+ EXPECT_EQ(NEW_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+
+ // Call setState which will update both state and previous state.
+ EXPECT_NO_THROW(setState(READY_ST));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(READY_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+
+ // Call setState again.
+ EXPECT_NO_THROW(setState(DO_WORK_ST));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(READY_ST, getPrevState());
+
+ // Verify that calling setState with an state that has no handler
+ // will throw.
+ EXPECT_THROW(setState(-1), StateModelError);
+
+ // Verify that calling setState with NEW_ST is ok.
+ EXPECT_NO_THROW(setState(NEW_ST));
+
+ // Verify that calling setState with END_ST is ok.
+ EXPECT_NO_THROW(setState(END_ST));
+
+ // Verify that calling setState with an undefined state throws.
+ EXPECT_THROW(setState(9999), StateModelError);
+}
+
+/// @brief Checks that invoking runModel prior to startModel is not allowed.
+TEST_F(StateModelTest, runBeforeStart) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+
+ // Attempt to call runModel before startModel. This should result in an
+ // orderly model failure.
+ ASSERT_NO_THROW(runModel(START_EVT));
+
+ // Check that state and event are correct.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(FAIL_EVT, getNextEvent());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+}
+
+/// @brief Tests that the endModel may be used to transition the model to
+/// a normal conclusion.
+TEST_F(StateModelTest, transitionWithEnd) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // Verify that state and event members are as expected.
+ EXPECT_EQ(DUMMY_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+ EXPECT_EQ(START_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(endModel());
+
+ // Verify state and event members are correctly set.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(DUMMY_ST, getPrevState());
+ EXPECT_EQ(END_EVT, getNextEvent());
+ EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+/// @brief Tests that the abortModel may be used to transition the model to
+/// failed conclusion.
+TEST_F(StateModelTest, transitionWithAbort) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // Verify that state and event members are as expected.
+ EXPECT_EQ(DUMMY_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+ EXPECT_EQ(START_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(abortModel("test invocation"));
+
+ // Verify state and event members are correctly set.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(DUMMY_ST, getPrevState());
+ EXPECT_EQ(FAIL_EVT, getNextEvent());
+ EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+/// @brief Tests that the boolean indicators for on state entry and exit
+/// work properly.
+TEST_F(StateModelTest, doFlags) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // Verify that "do" flags are false.
+ EXPECT_FALSE(doOnEntry());
+ EXPECT_FALSE(doOnExit());
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // We are transitioning states, so "do" flags should be true.
+ EXPECT_TRUE(doOnEntry());
+ EXPECT_TRUE(doOnExit());
+
+ // "do" flags are one-shots, so they should now both be false.
+ EXPECT_FALSE(doOnEntry());
+ EXPECT_FALSE(doOnExit());
+
+ // call transition to re-enter same state, "do" flags should be false.
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // "do" flags should be false.
+ EXPECT_FALSE(doOnEntry());
+ EXPECT_FALSE(doOnExit());
+
+}
+
+/// @brief Verifies that the model status methods accurately reflect the model
+/// status. It also verifies that the dictionaries can be modified before
+/// the model is running but not after.
+TEST_F(StateModelTest, statusMethods) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // After construction, state model is "new", all others should be false.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+
+ // Verify that events and states can be added before the model is started.
+ EXPECT_NO_THROW(defineEvent(9998, "9998"));
+ EXPECT_NO_THROW(defineState(9998, "9998",
+ std::bind(&StateModelTest::readyHandler,
+ this)));
+
+ // "START" the model.
+ // Fake out starting the model by calling transition to move from NEW_ST
+ // to DUMMY_ST with START_EVT. If we used startModel this would blow by
+ // the status of "running" but not "waiting".
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+
+ // Verify that events and states cannot be added after the model is started.
+ EXPECT_THROW(defineEvent(9999, "9999"), StateModelError);
+ EXPECT_THROW(defineState(9999, "9999",
+ std::bind(&StateModelTest::readyHandler, this)),
+ StateModelError);
+
+ // The state and event combos set above, should show the model as
+ // "running", all others should be false.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_TRUE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+
+ // call transition to submit NOP_EVT to current state, DUMMY_ST.
+ EXPECT_NO_THROW(transition(DUMMY_ST, NOP_EVT));
+
+ // Verify the status methods are correct: with next event set to NOP_EVT,
+ // model should be "running" and "waiting".
+ EXPECT_FALSE(isModelNew());
+ EXPECT_TRUE(isModelRunning());
+ EXPECT_TRUE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(endModel());
+
+ // With state set to END_ST, model should be done.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+}
+
+/// @brief Tests that the model status methods are correct after a model
+/// failure.
+TEST_F(StateModelTest, statusMethodsOnFailure) {
+ // Construct the event and state definitions, normally done by startModel.
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(abortModel("test invocation"));
+
+ // With state set to END_ST, model should be done.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+}
+
+/// @brief Checks that the context strings accurately reflect context and
+/// are safe to invoke.
+TEST_F(StateModelTest, contextStrs) {
+ // Verify context methods do not throw prior to dictionary init.
+ ASSERT_NO_THROW(getContextStr());
+ ASSERT_NO_THROW(getPrevContextStr());
+
+ // Construct the event and state definitions, normally done by startModel.
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+
+ // transition uses setState and setEvent, testing it tests all three.
+ EXPECT_NO_THROW(transition(READY_ST, START_EVT));
+
+ // Verify the current context string depicts correct state and event.
+ std::string ctx_str;
+ ASSERT_NO_THROW(ctx_str = getContextStr());
+ EXPECT_NE(std::string::npos, ctx_str.find(getStateLabel(READY_ST)));
+ EXPECT_NE(std::string::npos, ctx_str.find(getEventLabel(START_EVT)));
+
+ // Verify the previous context string depicts correct state and event.
+ ASSERT_NO_THROW(ctx_str = getPrevContextStr());
+ EXPECT_NE(std::string::npos, ctx_str.find(getStateLabel(NEW_ST)));
+ EXPECT_NE(std::string::npos, ctx_str.find(getEventLabel(NOP_EVT)));
+}
+
+/// @brief Tests that undefined states are handled gracefully.
+/// This test verifies that attempting to transition to an undefined state,
+/// which constitutes a model violation, results in an orderly model failure.
+TEST_F(StateModelTest, undefinedState) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // First, lets execute the state model to a known valid point, by
+ // calling startModel. This should run the model through to DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Resume the model with next event set to cause the DO_WORK_ST handler
+ // to transition to an undefined state. This should cause it to return
+ // without throwing and yield a failed model.
+ EXPECT_NO_THROW(runModel(FORCE_UNDEFINED_ST_EVT));
+
+ // Verify that status methods are correct: model is done but failed.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is still false.
+ EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Tests that an unexpected exception thrown by a state handler is
+/// handled gracefully. State models are supposed to account for and handle
+/// all errors that they actions (i.e. handlers) may cause. In the event they
+/// do not, this constitutes a model violation. This test verifies such
+/// violations are handled correctly and result in an orderly model failure.
+TEST_F(StateModelTest, unexpectedError) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // First, lets execute the state model to a known valid point, by
+ // calling startModel with a start state of READY_ST.
+ // This should run the model through to DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Resume the model with next event set to cause the DO_WORK_ST handler
+ // to transition to an undefined state. This should cause it to return
+ // without throwing and yield a failed model.
+ EXPECT_NO_THROW(runModel(SIMULATE_ERROR_EVT));
+
+ // Verify that status methods are correct: model is done but failed.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is still false.
+ EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Tests that undefined events are handled gracefully.
+/// This test verifies that submitting an undefined event to the state machine
+/// results, which constitutes a model violation, results in an orderly model
+/// failure.
+TEST_F(StateModelTest, undefinedEvent) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // First, lets execute the state model to a known valid point, by
+ // calling startModel with a start state of READY_ST.
+ // This should run the model through to DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Attempting to post an undefined event within runModel should cause it
+ // to return without throwing and yield a failed model.
+ EXPECT_NO_THROW(runModel(9999));
+
+ // Verify that status methods are correct: model is done but failed.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is still false.
+ EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Test the basic mechanics of state model execution.
+/// This test exercises the ability to execute state model from start to
+/// finish, including the handling of a asynchronous IO operation.
+TEST_F(StateModelTest, stateModelTest) {
+ // Verify that status methods are correct: model is new.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // Launch the transaction by calling startModel. The state model
+ // should run up until the simulated async work operation is initiated
+ // in DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify that we are now in state of DO_WORK_ST, the last event was
+ // WORK_START_EVT, the next event is NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(WORK_START_EVT, getLastEvent());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Simulate completion of async work completion by resuming runModel with
+ // an event of WORK_DONE_EVT.
+ ASSERT_NO_THROW(runModel(WORK_DONE_EVT));
+
+ // Verify that the state model has progressed through to completion:
+ // it is in the DONE_ST, the status is ST_COMPLETED, and the next event
+ // is NOP_EVT.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(END_EVT, getNextEvent());
+
+ // Verify that status methods are correct: model done.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+
+ // Verify that failure explanation is empty.
+ EXPECT_TRUE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is true.
+ EXPECT_TRUE(getWorkCompleted());
+}
+
+// This test verifies the pausing and un-pausing capabilities of the state
+// model.
+TEST_F(StateModelTest, stateModelPause) {
+ // Verify that status methods are correct: model is new.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(isModelPaused());
+
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // Transition straight to the state in which the model should always
+ // pause.
+ ASSERT_NO_THROW(startModel(PAUSE_ALWAYS_ST));
+
+ // Verify it was successful and that the model is paused.
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // Run the model again. It should still be paused.
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_TRUE(isModelPaused());
+
+ // Unpause the model and transition to the state in which the model
+ // should be paused at most once.
+ unpauseModel();
+ transition(PAUSE_ONCE_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // The model should still be paused until explicitly unpaused.
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ unpauseModel();
+
+ // Transition back to the first state. The model should pause again.
+ transition(PAUSE_ALWAYS_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // Unpause the model and transition to the state in which the model
+ // should pause only once. This time it should not pause.
+ unpauseModel();
+ transition(PAUSE_ONCE_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_FALSE(isModelPaused());
+}
+
+}
diff --git a/src/lib/util/tests/stopwatch_unittest.cc b/src/lib/util/tests/stopwatch_unittest.cc
new file mode 100644
index 0000000..a506b6b
--- /dev/null
+++ b/src/lib/util/tests/stopwatch_unittest.cc
@@ -0,0 +1,307 @@
+// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/stopwatch.h>
+#include <util/stopwatch_impl.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::util;
+using namespace boost::posix_time;
+
+/// @brief @c StopwatchImpl mock object.
+///
+/// This class derives from the @c StopwatchImpl to override the
+/// @c StopwatchImpl::getCurrentTime. This method is internally called by
+/// the @c StopwatchImpl to determine the current time. By providing the
+/// implementation of this method which returns the fixed (well known)
+/// timestamp value we can obtain the deterministic values from the accessors
+/// of this class.
+///
+/// This class also includes some convenience methods to return the time
+/// durations in milliseconds.
+class StopwatchMock : public StopwatchImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param ref_time Reference time, i.e. the arbitrary time value from
+ /// which time is measured. The @c current_time_ value returned by the
+ /// @c StopwatchMock::getCurrentTime is initialized to this value.
+ /// Subsequent calls to the @c StopwatchMock::ffwd move the value of
+ /// the @c current_time_ forward.
+ StopwatchMock(const ptime& ref_time);
+
+ /// @brief Fast forward time.
+ ///
+ /// Moves the value of the @c current_time_ forward by the specified
+ /// number of milliseconds (microseconds). As a result the timestamp
+ /// returned by the @c StopwatchMock::getCurrentTime moves by this value.
+ /// This simulates the time progress.
+ ///
+ /// @param ms Specifies the number of milliseconds to move current time.
+ /// @param us Specifies the number of fractional microseconds to move
+ /// current time.
+ void ffwd(const uint32_t ms, const uint32_t us = 0);
+
+ /// @brief Returns the last duration in milliseconds.
+ uint32_t getLastDurationInMs() const;
+
+ /// @brief Returns the total duration in milliseconds.
+ uint32_t getTotalDurationInMs() const;
+
+protected:
+
+ /// @brief Returns the current time.
+ ///
+ /// This method returns the fixed @c current_time_ timestamp.
+ virtual ptime getCurrentTime() const;
+
+private:
+
+ /// @brief Holds the current time to be returned by the
+ /// @c StopwatchMock::getCurrentTime.
+ ptime current_time_;
+
+};
+
+StopwatchMock::StopwatchMock(const ptime& ref_time)
+ : StopwatchImpl(), current_time_(ref_time) {
+}
+
+void
+StopwatchMock::ffwd(const uint32_t ms, const uint32_t us) {
+ current_time_ += milliseconds(ms);
+ current_time_ += microseconds(us);
+}
+
+uint32_t
+StopwatchMock::getLastDurationInMs() const {
+ return (getLastDuration().total_milliseconds());
+}
+
+uint32_t
+StopwatchMock::getTotalDurationInMs() const {
+ return (getTotalDuration().total_milliseconds());
+}
+
+ptime
+StopwatchMock::getCurrentTime() const {
+ return (current_time_);
+}
+
+/// @brief Test fixture class for testing @c StopwatchImpl.
+class StopwatchTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ StopwatchTest() = default;
+
+ /// @brief Destructor
+ virtual ~StopwatchTest() = default;
+
+protected:
+
+ /// @brief Set up the test.
+ ///
+ /// Initializes the reference time to be used to create the instances
+ /// of the @c StopwatchMock objects.
+ virtual void SetUp();
+
+ /// @brief Holds the reference time to be used to create the instances
+ /// of the @c StopwatchMock objects.
+ ptime ref_time_;
+};
+
+void
+StopwatchTest::SetUp() {
+ ref_time_ = microsec_clock::universal_time();
+}
+
+/// This test checks the behavior of the stopwatch when it is started
+/// and stopped multiple times. It uses the StopwatchMock object to
+/// control the "time flow" by setting the current time to arbitrary
+/// values using the StopwatchMock::ffwd. In addition, this test
+/// checks that the stopwatch can be reset.
+TEST_F(StopwatchTest, multipleMeasurements) {
+ StopwatchMock stopwatch(ref_time_);
+ // The stopwatch shouldn't automatically start. The initial
+ // durations should be set to 0.
+ EXPECT_EQ(0, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(0, stopwatch.getTotalDurationInMs());
+
+ stopwatch.start();
+
+ // Even though the stopwatch is started, the time is still set to
+ // the initial value. The durations should not be affected.
+ EXPECT_EQ(0, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(0, stopwatch.getTotalDurationInMs());
+
+ // Move the time by 10 ms.
+ stopwatch.ffwd(10);
+
+ // It should be possible to retrieve the durations even when the
+ // stopwatch is running.
+ EXPECT_EQ(10, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(10, stopwatch.getTotalDurationInMs());
+
+ // Now stop it and make sure that the same values are returned.
+ stopwatch.stop();
+
+ EXPECT_EQ(10, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(10, stopwatch.getTotalDurationInMs());
+
+ // Start it again, but don't move the time forward yet.
+ stopwatch.start();
+
+ // The new duration should be 0, but the total should be equal to
+ // the previously measured duration.
+ EXPECT_EQ(0, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(10, stopwatch.getTotalDurationInMs());
+
+ // Move time by 5 ms.
+ stopwatch.ffwd(5);
+
+ // New measured duration should be 5 ms. The total should be 15 ms.
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
+
+ // Stop it again and make sure the values returned are the same.
+ stopwatch.stop();
+
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
+
+ // Move the time forward while the stopwatch is stopped.
+ stopwatch.ffwd(8);
+
+ // The measured values should not be affected.
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
+
+ // Stop should be no-op in this case.
+ stopwatch.stop();
+
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
+
+ // Start the stopwatch again.
+ stopwatch.start();
+
+ // Move time by 3 ms.
+ stopwatch.ffwd(3);
+
+ // Since the stopwatch is running, the measured duration should
+ // get updated again.
+ EXPECT_EQ(3, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(18, stopwatch.getTotalDurationInMs());
+
+ // Move the time by 2 ms.
+ stopwatch.ffwd(2);
+
+ // Start should be no-op in this case.
+ stopwatch.start();
+
+ // But the durations should be updated.
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(20, stopwatch.getTotalDurationInMs());
+
+ // Make sure we can reset.
+ stopwatch.reset();
+
+ EXPECT_EQ(0, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(0, stopwatch.getTotalDurationInMs());
+}
+
+// This test checks that the stopwatch works when the real clock is in use.
+TEST_F(StopwatchTest, realTime) {
+ // Initially, the measured time should be 0.
+ Stopwatch stopwatch;
+ EXPECT_EQ(0, stopwatch.getLastMilliseconds());
+ EXPECT_EQ(0, stopwatch.getTotalMilliseconds());
+
+ // Start the stopwatch.
+ stopwatch.start();
+
+ // Sleep for 1 ms. The stopwatch should measure this duration.
+ usleep(1000);
+
+ stopwatch.stop();
+
+ // The measured duration should be greater or equal 1 ms.
+ long current_duration = stopwatch.getLastMilliseconds();
+ EXPECT_GE(current_duration, 1);
+ EXPECT_EQ(current_duration, stopwatch.getTotalMilliseconds());
+
+ // Sleep for another 2 ms while the stopwatch is in the stopped state.
+ usleep(2000);
+
+ // In the stopped state, we should still have old durations measured.
+ EXPECT_EQ(current_duration, stopwatch.getLastMilliseconds());
+ EXPECT_EQ(current_duration, stopwatch.getTotalMilliseconds());
+
+ // Start it again.
+ stopwatch.start();
+
+ // Sleep for 1 ms.
+ usleep(1000);
+
+ // The durations should get updated as appropriate.
+ EXPECT_GE(stopwatch.getLastMilliseconds(), 1);
+ EXPECT_GE(stopwatch.getTotalMilliseconds(), 2);
+}
+
+// Make sure that we can obtain the durations as microseconds.
+TEST_F(StopwatchTest, getLastMicroseconds) {
+ Stopwatch stopwatch;
+ stopwatch.start();
+
+ usleep(1000);
+
+ stopwatch.stop();
+
+ long current_duration = stopwatch.getLastMicroseconds();
+ EXPECT_GE(current_duration, 1000);
+ EXPECT_EQ(current_duration, stopwatch.getTotalMicroseconds());
+}
+
+// Make sure that we can use the "autostart" option to start the time
+// measurement in the constructor.
+TEST_F(StopwatchTest, autostart) {
+ Stopwatch stopwatch(true);
+ usleep(1000);
+
+ stopwatch.stop();
+
+ EXPECT_GE(stopwatch.getLastMilliseconds(), 1);
+ EXPECT_EQ(stopwatch.getLastMilliseconds(), stopwatch.getTotalMilliseconds());
+}
+
+// Make sure that the conversion to the loggable string works as expected.
+TEST_F(StopwatchTest, logFormat) {
+ time_duration duration = microseconds(223043);
+ EXPECT_EQ("223.043 ms", StopwatchImpl::logFormat(duration));
+
+ duration = microseconds(1234);
+ EXPECT_EQ("1.234 ms", StopwatchImpl::logFormat(duration));
+
+ duration = microseconds(2000);
+ EXPECT_EQ("2.000 ms", StopwatchImpl::logFormat(duration));
+
+ duration = milliseconds(2100);
+ EXPECT_EQ("2.10 s", StopwatchImpl::logFormat(duration));
+
+ duration = milliseconds(3123);
+ EXPECT_EQ("3.12 s", StopwatchImpl::logFormat(duration));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/strutil_unittest.cc b/src/lib/util/tests/strutil_unittest.cc
new file mode 100644
index 0000000..912b40f
--- /dev/null
+++ b/src/lib/util/tests/strutil_unittest.cc
@@ -0,0 +1,642 @@
+// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <util/strutil.h>
+#include <util/encode/hex.h>
+
+#include <gtest/gtest.h>
+
+#include <stdint.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::util::str;
+using namespace std;
+
+namespace {
+
+// Check for slash replacement
+
+TEST(StringUtilTest, Slash) {
+
+ string instring = "";
+ isc::util::str::normalizeSlash(instring);
+ EXPECT_EQ("", instring);
+
+ instring = "C:\\A\\B\\C.D";
+ isc::util::str::normalizeSlash(instring);
+ EXPECT_EQ("C:/A/B/C.D", instring);
+
+ instring = "// \\ //";
+ isc::util::str::normalizeSlash(instring);
+ EXPECT_EQ("// / //", instring);
+}
+
+// Check that leading and trailing space trimming works
+
+TEST(StringUtilTest, Trim) {
+
+ // Empty and full string.
+ EXPECT_EQ("", isc::util::str::trim(""));
+ EXPECT_EQ("abcxyz", isc::util::str::trim("abcxyz"));
+
+ // Trim right-most blanks
+ EXPECT_EQ("ABC", isc::util::str::trim("ABC "));
+ EXPECT_EQ("ABC", isc::util::str::trim("ABC\t\t \n\t"));
+
+ // Left-most blank trimming
+ EXPECT_EQ("XYZ", isc::util::str::trim(" XYZ"));
+ EXPECT_EQ("XYZ", isc::util::str::trim("\t\t \tXYZ"));
+
+ // Right and left, with embedded spaces
+ EXPECT_EQ("MN \t OP", isc::util::str::trim("\t\tMN \t OP \t"));
+}
+
+// Check tokenization. Note that ASSERT_EQ is used to check the size of the
+// returned vector; if not as expected, the following references may be invalid
+// so should not be used.
+
+TEST(StringUtilTest, Tokens) {
+ vector<string> result;
+
+ // Default delimiters
+
+ // Degenerate cases
+ result = isc::util::str::tokens(""); // Empty string
+ EXPECT_EQ(0, result.size());
+
+ result = isc::util::str::tokens(" \n "); // String is all delimiters
+ EXPECT_EQ(0, result.size());
+
+ result = isc::util::str::tokens("abc"); // String has no delimiters
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("abc"), result[0]);
+
+ // String containing leading and/or trailing delimiters, no embedded ones.
+ result = isc::util::str::tokens("\txyz"); // One leading delimiter
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::util::str::tokens("\t \nxyz"); // Multiple leading delimiters
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::util::str::tokens("xyz\n"); // One trailing delimiter
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::util::str::tokens("xyz \t"); // Multiple trailing
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::util::str::tokens("\t xyz \n"); // Leading and trailing
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ // Embedded delimiters
+ result = isc::util::str::tokens("abc\ndef"); // 2 tokens, one separator
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+
+ result = isc::util::str::tokens("abc\t\t\ndef"); // 2 tokens, 3 separators
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+
+ result = isc::util::str::tokens("abc\n \tdef\t\tghi");
+ ASSERT_EQ(3, result.size()); // Multiple tokens, many delims
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+ EXPECT_EQ(string("ghi"), result[2]);
+
+ // Embedded and non-embedded delimiters
+
+ result = isc::util::str::tokens("\t\t \nabc\n \tdef\t\tghi \n\n");
+ ASSERT_EQ(3, result.size()); // Multiple tokens, many delims
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+ EXPECT_EQ(string("ghi"), result[2]);
+
+ // Non-default delimiter
+ result = isc::util::str::tokens("alpha/beta/ /gamma//delta/epsilon/", "/");
+ ASSERT_EQ(6, result.size());
+ EXPECT_EQ(string("alpha"), result[0]);
+ EXPECT_EQ(string("beta"), result[1]);
+ EXPECT_EQ(string(" "), result[2]);
+ EXPECT_EQ(string("gamma"), result[3]);
+ EXPECT_EQ(string("delta"), result[4]);
+ EXPECT_EQ(string("epsilon"), result[5]);
+
+ // Non-default delimiters (plural)
+ result = isc::util::str::tokens("+*--alpha*beta+ -gamma**delta+epsilon-+**",
+ "*+-");
+ ASSERT_EQ(6, result.size());
+ EXPECT_EQ(string("alpha"), result[0]);
+ EXPECT_EQ(string("beta"), result[1]);
+ EXPECT_EQ(string(" "), result[2]);
+ EXPECT_EQ(string("gamma"), result[3]);
+ EXPECT_EQ(string("delta"), result[4]);
+ EXPECT_EQ(string("epsilon"), result[5]);
+
+ // Escaped delimiter
+ result = isc::util::str::tokens("foo\\,bar", ",", true);
+ EXPECT_EQ(1, result.size());
+ EXPECT_EQ(string("foo,bar"), result[0]);
+
+ // Escaped escape
+ result = isc::util::str::tokens("foo\\\\,bar", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("foo\\"), result[0]);
+ EXPECT_EQ(string("bar"), result[1]);
+
+ // Double escapes
+ result = isc::util::str::tokens("foo\\\\\\\\,\\bar", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("foo\\\\"), result[0]);
+ EXPECT_EQ(string("\\bar"), result[1]);
+
+ // Escaped standard character
+ result = isc::util::str::tokens("fo\\o,bar", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("fo\\o"), result[0]);
+ EXPECT_EQ(string("bar"), result[1]);
+
+ // Escape at the end
+ result = isc::util::str::tokens("foo,bar\\", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("foo"), result[0]);
+ EXPECT_EQ(string("bar\\"), result[1]);
+
+ // Escape opening a token
+ result = isc::util::str::tokens("foo,\\,,bar", ",", true);
+ ASSERT_EQ(3, result.size());
+ EXPECT_EQ(string("foo"), result[0]);
+ EXPECT_EQ(string(","), result[1]);
+ EXPECT_EQ(string("bar"), result[2]);
+}
+
+// Changing case
+
+TEST(StringUtilTest, ChangeCase) {
+ string mixed("abcDEFghiJKLmno123[]{=+--+]}");
+ string upper("ABCDEFGHIJKLMNO123[]{=+--+]}");
+ string lower("abcdefghijklmno123[]{=+--+]}");
+
+ string test = mixed;
+ isc::util::str::lowercase(test);
+ EXPECT_EQ(lower, test);
+
+ test = mixed;
+ isc::util::str::uppercase(test);
+ EXPECT_EQ(upper, test);
+}
+
+// Formatting
+
+TEST(StringUtilTest, Formatting) {
+
+ vector<string> args;
+ args.push_back("arg1");
+ args.push_back("arg2");
+ args.push_back("arg3");
+
+ string format1 = "This is a string with no tokens";
+ EXPECT_EQ(format1, isc::util::str::format(format1, args));
+
+ string format2 = ""; // Empty string
+ EXPECT_EQ(format2, isc::util::str::format(format2, args));
+
+ string format3 = " "; // Empty string
+ EXPECT_EQ(format3, isc::util::str::format(format3, args));
+
+ string format4 = "String with %d non-string tokens %lf";
+ EXPECT_EQ(format4, isc::util::str::format(format4, args));
+
+ string format5 = "String with %s correct %s number of tokens %s";
+ string result5 = "String with arg1 correct arg2 number of tokens arg3";
+ EXPECT_EQ(result5, isc::util::str::format(format5, args));
+
+ string format6 = "String with %s too %s few tokens";
+ string result6 = "String with arg1 too arg2 few tokens";
+ EXPECT_EQ(result6, isc::util::str::format(format6, args));
+
+ string format7 = "String with %s too %s many %s tokens %s !";
+ string result7 = "String with arg1 too arg2 many arg3 tokens %s !";
+ EXPECT_EQ(result7, isc::util::str::format(format7, args));
+
+ string format8 = "String with embedded%s%s%stokens";
+ string result8 = "String with embeddedarg1arg2arg3tokens";
+ EXPECT_EQ(result8, isc::util::str::format(format8, args));
+
+ // Handle an empty vector
+ args.clear();
+ string format9 = "%s %s";
+ EXPECT_EQ(format9, isc::util::str::format(format9, args));
+}
+
+TEST(StringUtilTest, getToken) {
+ string s("a b c");
+ istringstream ss(s);
+ EXPECT_EQ("a", isc::util::str::getToken(ss));
+ EXPECT_EQ("b", isc::util::str::getToken(ss));
+ EXPECT_EQ("c", isc::util::str::getToken(ss));
+ EXPECT_THROW(isc::util::str::getToken(ss), isc::util::str::StringTokenError);
+}
+
+int32_t tokenToNumCall_32_16(const string& token) {
+ return isc::util::str::tokenToNum<int32_t, 16>(token);
+}
+
+int16_t tokenToNumCall_16_8(const string& token) {
+ return isc::util::str::tokenToNum<int16_t, 8>(token);
+}
+
+TEST(StringUtilTest, tokenToNum) {
+ uint32_t num32 = tokenToNumCall_32_16("0");
+ EXPECT_EQ(0, num32);
+ num32 = tokenToNumCall_32_16("123");
+ EXPECT_EQ(123, num32);
+ num32 = tokenToNumCall_32_16("65535");
+ EXPECT_EQ(65535, num32);
+
+ EXPECT_THROW(tokenToNumCall_32_16(""),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("a"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("-1"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("65536"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("1234567890"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("-1234567890"),
+ isc::util::str::StringTokenError);
+
+ uint16_t num16 = tokenToNumCall_16_8("123");
+ EXPECT_EQ(123, num16);
+ num16 = tokenToNumCall_16_8("0");
+ EXPECT_EQ(0, num16);
+ num16 = tokenToNumCall_16_8("255");
+ EXPECT_EQ(255, num16);
+
+ EXPECT_THROW(tokenToNumCall_16_8(""),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("a"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("-1"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("256"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("1234567890"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("-1234567890"),
+ isc::util::str::StringTokenError);
+
+}
+
+/// @brief Convenience function which calls quotedStringToBinary
+/// and converts returned vector back to string.
+///
+/// @param s Input string.
+/// @return String holding a copy of a vector returned by the
+/// quotedStringToBinary.
+std::string testQuoted(const std::string& s) {
+ std::vector<uint8_t> vec = str::quotedStringToBinary(s);
+ std::string s2(vec.begin(), vec.end());
+ return (s2);
+}
+
+TEST(StringUtilTest, quotedStringToBinary) {
+ // No opening or closing quote should result in empty string.
+ EXPECT_TRUE(str::quotedStringToBinary("'").empty());
+ EXPECT_TRUE(str::quotedStringToBinary("").empty());
+ EXPECT_TRUE(str::quotedStringToBinary(" ").empty());
+ EXPECT_TRUE(str::quotedStringToBinary("'circuit id").empty());
+ EXPECT_TRUE(str::quotedStringToBinary("circuit id'").empty());
+
+ // If there is only opening and closing quote, an empty
+ // vector should be returned.
+ EXPECT_TRUE(str::quotedStringToBinary("''").empty());
+
+ // Both opening and ending quote is present.
+ EXPECT_EQ("circuit id", testQuoted("'circuit id'"));
+ EXPECT_EQ("remote id", testQuoted(" ' remote id'"));
+ EXPECT_EQ("duid", testQuoted(" ' duid'"));
+ EXPECT_EQ("duid", testQuoted("'duid ' "));
+ EXPECT_EQ("remote'id", testQuoted(" ' remote'id '"));
+ EXPECT_EQ("remote id'", testQuoted("'remote id''"));
+ EXPECT_EQ("'remote id", testQuoted("''remote id'"));
+
+ // Multiple quotes.
+ EXPECT_EQ("'", testQuoted("'''"));
+ EXPECT_EQ("''", testQuoted("''''"));
+}
+
+/// @brief Test that hex string with colons can be decoded.
+///
+/// @param input Input string to be decoded.
+/// @param reference A string without colons representing the
+/// decoded data.
+void testColonSeparated(const std::string& input,
+ const std::string& reference) {
+ // Create a reference vector.
+ std::vector<uint8_t> reference_vector;
+ ASSERT_NO_THROW(encode::decodeHex(reference, reference_vector));
+
+ // Fill the output vector with some garbage to make sure that
+ // the data is erased when a string is decoded successfully.
+ std::vector<uint8_t> decoded(1, 10);
+ ASSERT_NO_THROW(decodeColonSeparatedHexString(input, decoded));
+
+ // Get the string representation of the decoded data for logging
+ // purposes.
+ std::string encoded;
+ ASSERT_NO_THROW(encoded = encode::encodeHex(decoded));
+
+ // Check if the decoded data matches the reference.
+ EXPECT_TRUE(decoded == reference_vector)
+ << "decoded data don't match the reference, input='"
+ << input << "', reference='" << reference << "'"
+ ", decoded='" << encoded << "'";
+}
+
+TEST(StringUtilTest, decodeColonSeparatedHexString) {
+ // Test valid strings.
+ testColonSeparated("A1:02:C3:d4:e5:F6", "A102C3D4E5F6");
+ testColonSeparated("A:02:3:d:E5:F6", "0A02030DE5F6");
+ testColonSeparated("A:B:C:D", "0A0B0C0D");
+ testColonSeparated("1", "01");
+ testColonSeparated("1e", "1E");
+ testColonSeparated("", "");
+
+ // Test invalid strings.
+ std::vector<uint8_t> decoded;
+ // Whitespaces.
+ EXPECT_THROW(decodeColonSeparatedHexString(" ", decoded),
+ isc::BadValue);
+ // Whitespace before digits.
+ EXPECT_THROW(decodeColonSeparatedHexString(" A1", decoded),
+ isc::BadValue);
+ // Two consecutive colons.
+ EXPECT_THROW(decodeColonSeparatedHexString("A::01", decoded),
+ isc::BadValue);
+ // Three consecutive colons.
+ EXPECT_THROW(decodeColonSeparatedHexString("A:::01", decoded),
+ isc::BadValue);
+ // Whitespace within a string.
+ EXPECT_THROW(decodeColonSeparatedHexString("A :01", decoded),
+ isc::BadValue);
+ // Terminating colon.
+ EXPECT_THROW(decodeColonSeparatedHexString("0A:01:", decoded),
+ isc::BadValue);
+ // Opening colon.
+ EXPECT_THROW(decodeColonSeparatedHexString(":0A:01", decoded),
+ isc::BadValue);
+ // Three digits before the colon.
+ EXPECT_THROW(decodeColonSeparatedHexString("0A1:B1", decoded),
+ isc::BadValue);
+}
+
+void testFormatted(const std::string& input,
+ const std::string& reference) {
+ // Create a reference vector.
+ std::vector<uint8_t> reference_vector;
+ ASSERT_NO_THROW(encode::decodeHex(reference, reference_vector));
+
+ // Fill the output vector with some garbage to make sure that
+ // the data is erased when a string is decoded successfully.
+ std::vector<uint8_t> decoded(1, 10);
+ ASSERT_NO_THROW(decodeFormattedHexString(input, decoded));
+
+ // Get the string representation of the decoded data for logging
+ // purposes.
+ std::string encoded;
+ ASSERT_NO_THROW(encoded = encode::encodeHex(decoded));
+
+ // Check if the decoded data matches the reference.
+ EXPECT_TRUE(decoded == reference_vector)
+ << "decoded data don't match the reference, input='"
+ << input << "', reference='" << reference << "'"
+ ", decoded='" << encoded << "'";
+}
+
+TEST(StringUtilTest, decodeFormattedHexString) {
+ // Colon separated.
+ testFormatted("1:A7:B5:4:23", "01A7B50423");
+ // Space separated.
+ testFormatted("1 A7 B5 4 23", "01A7B50423");
+ // No colons, even number of digits.
+ testFormatted("17a534", "17A534");
+ // Odd number of digits.
+ testFormatted("A3A6f78", "0A3A6F78");
+ // '0x' prefix.
+ testFormatted("0xA3A6f78", "0A3A6F78");
+ // '0x' prefix with a special value of 0.
+ testFormatted("0x0", "00");
+ // Empty string.
+ testFormatted("", "");
+
+ std::vector<uint8_t> decoded;
+ // Dangling colon.
+ EXPECT_THROW(decodeFormattedHexString("0a:", decoded),
+ isc::BadValue);
+ // Dangling space.
+ EXPECT_THROW(decodeFormattedHexString("0a ", decoded),
+ isc::BadValue);
+ // '0x' prefix and spaces.
+ EXPECT_THROW(decodeFormattedHexString("0x01 02", decoded),
+ isc::BadValue);
+ // '0x' prefix and colons.
+ EXPECT_THROW(decodeFormattedHexString("0x01:02", decoded),
+ isc::BadValue);
+ // colon and spaces mixed
+ EXPECT_THROW(decodeFormattedHexString("01:02 03", decoded),
+ isc::BadValue);
+ // Missing colon.
+ EXPECT_THROW(decodeFormattedHexString("01:0203", decoded),
+ isc::BadValue);
+ // Missing space.
+ EXPECT_THROW(decodeFormattedHexString("01 0203", decoded),
+ isc::BadValue);
+ // Invalid prefix.
+ EXPECT_THROW(decodeFormattedHexString("x0102", decoded),
+ isc::BadValue);
+ // Invalid prefix again.
+ EXPECT_THROW(decodeFormattedHexString("1x0102", decoded),
+ isc::BadValue);
+}
+
+/// @brief Function used to test StringSantitizer
+/// @param original - string to sanitize
+/// @param char_set - regular expression string describing invalid
+/// characters
+/// @param char_replacement - character(s) which replace invalid
+/// characters
+/// @param expected - expected sanitized string
+void sanitizeStringTest(
+ const std::string& original,
+ const std::string& char_set,
+ const std::string& char_replacement,
+ const std::string& expected) {
+
+ StringSanitizerPtr ss;
+ std::string sanitized;
+
+ try {
+ ss.reset(new StringSanitizer(char_set, char_replacement));
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "Could not construct sanitizer:" << ex.what();
+ return;
+ }
+
+ try {
+ sanitized = ss->scrub(original);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "Could not scrub string:" << ex.what();
+ return;
+ }
+
+ EXPECT_EQ(sanitized, expected);
+}
+
+// Verifies StringSantizer class
+TEST(StringUtilTest, stringSanitizer) {
+ // Bad regular expression should throw.
+ StringSanitizerPtr ss;
+ ASSERT_THROW(ss.reset(new StringSanitizer("[bogus-regex","")), BadValue);
+
+ std::string good_data(StringSanitizer::MAX_DATA_SIZE, '0');
+ std::string bad_data(StringSanitizer::MAX_DATA_SIZE + 1, '0');
+
+ ASSERT_NO_THROW(ss.reset(new StringSanitizer(good_data, good_data)));
+
+ ASSERT_THROW(ss.reset(new StringSanitizer(bad_data, "")), BadValue);
+ ASSERT_THROW(ss.reset(new StringSanitizer("", bad_data)), BadValue);
+
+ // List of invalid chars should work: (b,c,2 are invalid)
+ sanitizeStringTest("abc.123", "[b-c2]", "*",
+ "a**.1*3");
+ // Inverted list of valid chars should work: (b,c,2 are valid)
+ sanitizeStringTest("abc.123", "[^b-c2]", "*",
+ "*bc**2*");
+
+ // A string of all valid chars should return an identical string.
+ sanitizeStringTest("-_A--B__Cabc34567_-", "[^A-Ca-c3-7_-]", "x",
+ "-_A--B__Cabc34567_-");
+
+ // Replacing with a character should work.
+ sanitizeStringTest("A[b]c\12JoE3-_x!B$Y#e", "[^A-Za-z0-9_]", "*",
+ "A*b*c*JoE3*_x*B*Y*e");
+
+ // Removing (i.e.replacing with an "empty" string) should work.
+ sanitizeStringTest("A[b]c\12JoE3-_x!B$Y#e", "[^A-Za-z0-9_]", "",
+ "AbcJoE3_xBYe");
+
+ // More than one non-matching in a row should work.
+ sanitizeStringTest("%%A%%B%%C%%", "[^A-Za-z0-9_]", "x",
+ "xxAxxBxxCxx");
+
+ // Removing more than one non-matching in a row should work.
+ sanitizeStringTest("%%A%%B%%C%%", "[^A-Za-z0-9_]", "",
+ "ABC");
+
+ // Replacing with a string should work.
+ sanitizeStringTest("%%A%%B%%C%%", "[^A-Za-z0-9_]", "xyz",
+ "xyzxyzAxyzxyzBxyzxyzCxyzxyz");
+
+ // Dots as valid chars work.
+ sanitizeStringTest("abc.123", "[^A-Za-z0-9_.]", "*",
+ "abc.123");
+
+ std::string withNulls("\000ab\000c.12\0003",10);
+ sanitizeStringTest(withNulls, "[^A-Za-z0-9_.]", "*",
+ "*ab*c.12*3");
+}
+
+// Verifies templated buffer iterator seekTrimmed() function
+TEST(StringUtilTest, seekTrimmed) {
+
+ // Empty buffer should be fine.
+ std::vector<uint8_t> buffer;
+ auto begin = buffer.end();
+ auto end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(0, std::distance(begin, end));
+
+ // Buffer of only trim values, should be fine.
+ buffer = { 1, 1 };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 1));
+ EXPECT_EQ(0, std::distance(begin, end));
+
+ // One trailing null should trim off.
+ buffer = {'o', 'n', 'e', 0 };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(3, std::distance(begin, end));
+
+ // More than one trailing null should trim off.
+ buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(5, std::distance(begin, end));
+
+ // Embedded null should be left in place.
+ buffer = { 'e', 'm', 0, 'b', 'e', 'd' };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(6, std::distance(begin, end));
+
+ // Leading null should be left in place.
+ buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(8, std::distance(begin, end));
+}
+
+// Verifies isPrintable predicate on strings.
+TEST(StringUtilTest, stringIsPrintable) {
+ string content;
+
+ // Empty is printable.
+ EXPECT_TRUE(isPrintable(content));
+
+ // Check Abcd.
+ content = "Abcd";
+ EXPECT_TRUE(isPrintable(content));
+
+ // Add a control character (not printable).
+ content += "\a";
+ EXPECT_FALSE(isPrintable(content));
+}
+
+// Verifies isPrintable predicate on byte vectors.
+TEST(StringUtilTest, vectorIsPrintable) {
+ vector<uint8_t> content;
+
+ // Empty is printable.
+ EXPECT_TRUE(isPrintable(content));
+
+ // Check Abcd.
+ content = { 0x41, 0x62, 0x63, 0x64 };
+ EXPECT_TRUE(isPrintable(content));
+
+ // Add a control character (not printable).
+ content.push_back('\a');
+ EXPECT_FALSE(isPrintable(content));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/thread_pool_unittest.cc b/src/lib/util/tests/thread_pool_unittest.cc
new file mode 100644
index 0000000..9c636c9
--- /dev/null
+++ b/src/lib/util/tests/thread_pool_unittest.cc
@@ -0,0 +1,661 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+#include <util/thread_pool.h>
+
+#include <signal.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief define CallBack type
+typedef function<void()> CallBack;
+
+/// @brief Test Fixture for testing isc::dhcp::ThreadPool
+class ThreadPoolTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ThreadPoolTest() : thread_count_(0), count_(0), wait_(false) {
+ }
+
+ /// @brief task function which registers the thread id and signals main
+ /// thread to stop waiting and then waits for main thread to signal to exit
+ void runAndWait() {
+ // run task
+ run();
+ // wait for main thread signal to exit
+ unique_lock<mutex> lk(wait_mutex_);
+ wait_cv_.wait(lk, [&]() {return (wait() == false);});
+ }
+
+ /// @brief task function which registers the thread id and signals main
+ /// thread to stop waiting
+ void run() {
+ {
+ // make sure this thread has started and it is accounted for
+ lock_guard<mutex> lk(mutex_);
+ auto id = this_thread::get_id();
+ // register this thread as doing work on items
+ ids_.emplace(id);
+ // finish task
+ ++count_;
+ // register this task on the history of this thread
+ history_[id].push_back(count_);
+ }
+ // wake main thread if it is waiting for this thread to process
+ cv_.notify_all();
+ }
+
+ /// @brief task function which tries to stop the thread pool and then calls
+ /// @ref runAndWait
+ void runStopResetAndWait(ThreadPool<CallBack>* thread_pool) {
+ EXPECT_THROW(thread_pool->stop(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(thread_pool->reset(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(thread_pool->wait(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(thread_pool->wait(0), MultiThreadingInvalidOperation);
+ sigset_t nsset;
+ pthread_sigmask(SIG_SETMASK, 0, &nsset);
+ EXPECT_EQ(1, sigismember(&nsset, SIGCHLD));
+ EXPECT_EQ(1, sigismember(&nsset, SIGINT));
+ EXPECT_EQ(1, sigismember(&nsset, SIGHUP));
+ EXPECT_EQ(1, sigismember(&nsset, SIGTERM));
+ EXPECT_NO_THROW(runAndWait());
+ }
+
+ /// @brief reset all counters and internal test state
+ void reset(uint32_t thread_count) {
+ // stop test threads
+ stopThreads();
+ // reset the internal state of the test
+ thread_count_ = thread_count;
+ count_ = 0;
+ wait_ = true;
+ ids_.clear();
+ history_.clear();
+ }
+
+ /// @brief start test threads and block main thread until all tasks have
+ /// been processed
+ ///
+ /// @param thread_count number of threads to be started
+ /// @param items_count number of items to wait for being processed
+ /// @param start create processing threads
+ /// @param signal give main thread control over the threads exit
+ void waitTasks(uint32_t thread_count, uint32_t items_count,
+ bool start = false, bool signal = true) {
+ // make sure we have all threads running when performing all the checks
+ unique_lock<mutex> lck(mutex_);
+ if (start) {
+ // start test threads if explicitly specified
+ startThreads(thread_count, signal);
+ }
+ // wait for the threads to process all the items
+ cv_.wait(lck, [&]() {return (count() == items_count);});
+ }
+
+ /// @brief start test threads
+ ///
+ /// @param thread_count number of threads to be started
+ /// @param signal give main thread control over the threads exit
+ void startThreads(uint32_t thread_count, bool signal = true) {
+ // set default task function to wait for main thread signal
+ auto runFunction = &ThreadPoolTest::runAndWait;
+ if (!signal) {
+ // set the task function to finish immediately
+ runFunction = &ThreadPoolTest::run;
+ }
+ // start test threads
+ for (uint32_t i = 0; i < thread_count; ++i) {
+ threads_.push_back(boost::make_shared<std::thread>(runFunction, this));
+ }
+ }
+
+ /// @brief stop test threads
+ void stopThreads() {
+ // signal threads that are waiting
+ signalThreads();
+ // wait for all test threads to exit
+ for (auto thread : threads_) {
+ thread->join();
+ }
+ // reset all threads
+ threads_.clear();
+ }
+
+ /// @brief function used by main thread to unblock processing threads
+ void signalThreads() {
+ {
+ lock_guard<mutex> lk(wait_mutex_);
+ // clear the wait flag so that threads will no longer wait for the main
+ // thread signal
+ wait_ = false;
+ }
+ // wake all threads if waiting for main thread signal
+ wait_cv_.notify_all();
+ }
+
+ /// @brief number of completed tasks
+ ///
+ /// @return the number of completed tasks
+ uint32_t count() {
+ return (count_);
+ }
+
+ /// @brief flag which indicates if working thread should wait for main
+ /// thread signal
+ ///
+ /// @return the wait flag
+ bool wait() {
+ return (wait_);
+ }
+
+ /// @brief check the total number of tasks that have been processed
+ /// Some of the tasks may have been run on the same thread and none may have
+ /// been processed by other threads
+ void checkRunHistory(uint32_t items_count) {
+ uint32_t count = 0;
+ // iterate over all threads history and count all the processed tasks
+ for (auto element : history_) {
+ count += element.second.size();
+ }
+ ASSERT_EQ(count, items_count);
+ }
+
+ /// @brief check the total number of threads that have processed tasks
+ void checkIds(uint32_t count) {
+ ASSERT_EQ(ids_.size(), count);
+ }
+
+private:
+ /// @brief thread count used by the test
+ uint32_t thread_count_;
+
+ /// @brief mutex used to keep the internal state consistent
+ std::mutex mutex_;
+
+ /// @brief condition variable used to signal main thread that all threads
+ /// have started processing
+ condition_variable cv_;
+
+ /// @brief mutex used to keep the internal state consistent
+ /// related to the control of the main thread over the working threads exit
+ std::mutex wait_mutex_;
+
+ /// @brief condition variable used to signal working threads to exit
+ condition_variable wait_cv_;
+
+ /// @brief number of completed tasks
+ uint32_t count_;
+
+ /// @brief flag which indicates if working thread should wait for main
+ /// thread signal
+ bool wait_;
+
+ /// @brief the set of thread ids which have completed tasks
+ set<std::thread::id> ids_;
+
+ /// @brief the list of completed tasks run by each thread
+ map<std::thread::id, list<uint32_t>> history_;
+
+ /// @brief the list of test threads
+ list<boost::shared_ptr<std::thread>> threads_;
+};
+
+/// @brief test ThreadPool add and count
+TEST_F(ThreadPoolTest, addAndCount) {
+ uint32_t items_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 4;
+
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+
+ // calling reset should clear all threads and should remove all queued items
+ EXPECT_NO_THROW(thread_pool.reset());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+}
+
+/// @brief test ThreadPool start and stop
+TEST_F(ThreadPoolTest, startAndStop) {
+ uint32_t items_count;
+ uint32_t thread_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 4;
+ thread_count = 4;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which block thread pool threads until signaled by main
+ // thread to force all threads of the thread pool to run exactly one task
+ call_back = std::bind(&ThreadPoolTest::runAndWait, this);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_THROW(thread_pool.start(0), InvalidParameter);
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // do it once again to check if it works
+ EXPECT_THROW(thread_pool.start(thread_count), InvalidOperation);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // do it once again to check if it works
+ EXPECT_THROW(thread_pool.stop(), InvalidOperation);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_THROW(thread_pool.stop(), InvalidOperation);
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling reset should clear all threads and should remove all queued items
+ EXPECT_NO_THROW(thread_pool.reset());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // do it once again to check if it works
+ EXPECT_NO_THROW(thread_pool.reset());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // add items to running thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // wait for all items to be processed
+ waitTasks(thread_count, items_count);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // as each thread pool thread is still waiting on main to unblock, each
+ // thread should have been registered in ids list
+ checkIds(items_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+
+ // signal thread pool tasks to continue
+ signalThreads();
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 64;
+ thread_count = 16;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which do not block the thread pool threads so that several
+ // tasks can be run on the same thread and some of the threads never even
+ // having a chance to run
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // wait for all items to be processed
+ waitTasks(thread_count, items_count);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 16;
+ thread_count = 16;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which try to stop the thread pool and then block thread pool
+ // threads until signaled by main thread to force all threads of the thread
+ // pool to run exactly one task
+ call_back = std::bind(&ThreadPoolTest::runStopResetAndWait, this, &thread_pool);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // add items to running thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // wait for all items to be processed
+ waitTasks(thread_count, items_count);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // as each thread pool thread is still waiting on main to unblock, each
+ // thread should have been registered in ids list
+ checkIds(items_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+
+ // signal thread pool tasks to continue
+ signalThreads();
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ /// statistics
+ std::cout << "stat10: " << thread_pool.getQueueStat(10) << std::endl;
+ std::cout << "stat100: " << thread_pool.getQueueStat(100) << std::endl;
+ std::cout << "stat1000: " << thread_pool.getQueueStat(1000) << std::endl;
+}
+
+/// @brief test ThreadPool wait
+TEST_F(ThreadPoolTest, wait) {
+ uint32_t items_count;
+ uint32_t thread_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 16;
+ thread_count = 16;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which block thread pool threads until signaled by main
+ // thread to force all threads of the thread pool to run exactly one task
+ call_back = std::bind(&ThreadPoolTest::runAndWait, this);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // wait for all items to be processed
+ waitTasks(thread_count, items_count);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // as each thread pool thread is still waiting on main to unblock, each
+ // thread should have been registered in ids list
+ checkIds(items_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+
+ // check that waiting on tasks does timeout
+ ASSERT_FALSE(thread_pool.wait(1));
+
+ // signal thread pool tasks to continue
+ signalThreads();
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 64;
+ thread_count = 16;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which do not block the thread pool threads so that several
+ // tasks can be run on the same thread and some of the threads never even
+ // having a chance to run
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // wait for all items to be processed
+ thread_pool.wait();
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+}
+
+/// @brief test ThreadPool max queue size
+TEST_F(ThreadPoolTest, maxQueueSize) {
+ uint32_t items_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 20;
+
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ bool ret = true;
+ for (uint32_t i = 0; i < items_count; ++i) {
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+
+ // change the max count
+ ASSERT_EQ(thread_pool.getMaxQueueSize(), 0);
+ size_t max_queue_size = 10;
+ thread_pool.setMaxQueueSize(max_queue_size);
+ EXPECT_EQ(thread_pool.getMaxQueueSize(), max_queue_size);
+
+ // adding an item should squeeze the queue
+ EXPECT_EQ(thread_pool.count(), items_count);
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_FALSE(ret);
+ EXPECT_EQ(thread_pool.count(), max_queue_size);
+}
+
+/// @brief test ThreadPool add front.
+TEST_F(ThreadPoolTest, addFront) {
+ uint32_t items_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 20;
+
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ bool ret = true;
+ for (uint32_t i = 0; i < items_count; ++i) {
+ EXPECT_NO_THROW(ret = thread_pool.addFront(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+
+ // change the max count
+ ASSERT_EQ(thread_pool.getMaxQueueSize(), 0);
+ size_t max_queue_size = 10;
+ thread_pool.setMaxQueueSize(max_queue_size);
+ EXPECT_EQ(thread_pool.getMaxQueueSize(), max_queue_size);
+
+ // adding an item at front should change nothing queue
+ EXPECT_EQ(thread_pool.count(), items_count);
+ EXPECT_NO_THROW(ret = thread_pool.addFront(boost::make_shared<CallBack>(call_back)));
+ EXPECT_FALSE(ret);
+ EXPECT_EQ(thread_pool.count(), items_count);
+}
+
+/// @brief test ThreadPool get queue statistics.
+TEST_F(ThreadPoolTest, getQueueStat) {
+ ThreadPool<CallBack> thread_pool;
+ EXPECT_THROW(thread_pool.getQueueStat(0), InvalidParameter);
+ EXPECT_THROW(thread_pool.getQueueStat(1), InvalidParameter);
+ EXPECT_THROW(thread_pool.getQueueStat(-10), InvalidParameter);
+ EXPECT_THROW(thread_pool.getQueueStat(10000), InvalidParameter);
+ EXPECT_NO_THROW(thread_pool.getQueueStat(10));
+ EXPECT_NO_THROW(thread_pool.getQueueStat(100));
+ EXPECT_NO_THROW(thread_pool.getQueueStat(1000));
+}
+
+} // namespace
diff --git a/src/lib/util/tests/time_utilities_unittest.cc b/src/lib/util/tests/time_utilities_unittest.cc
new file mode 100644
index 0000000..1637a7a
--- /dev/null
+++ b/src/lib/util/tests/time_utilities_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+
+#include <time.h>
+
+#include <util/time_utilities.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::util;
+
+// See time_utilities.cc
+namespace isc {
+namespace util {
+namespace detail {
+extern int64_t (*gettimeFunction)();
+}
+}
+}
+
+namespace {
+
+class DNSSECTimeTest : public ::testing::Test {
+protected:
+ ~DNSSECTimeTest() {
+ detail::gettimeFunction = NULL;
+ }
+};
+
+TEST_F(DNSSECTimeTest, fromText) {
+ // In most cases (in practice) the 32-bit and 64-bit versions should
+ // behave identically, so we'll mainly test the 32-bit version, which
+ // will be more commonly used in actual code (because many of the wire
+ // format time field are 32-bit). The subtle cases where these two
+ // return different values will be tested at the end of this test case.
+
+ // These are bogus and should be rejected
+ EXPECT_THROW(timeFromText32("2011 101120000"), InvalidTime);
+ EXPECT_THROW(timeFromText32("201101011200-0"), InvalidTime);
+
+ // Short length (or "decimal integer" version of representation;
+ // it's valid per RFC4034, but is not supported in this implementation)
+ EXPECT_THROW(timeFromText32("20100223"), InvalidTime);
+
+ // Leap year checks
+ EXPECT_THROW(timeFromText32("20110229120000"), InvalidTime);
+ EXPECT_THROW(timeFromText32("21000229120000"), InvalidTime);
+ EXPECT_NO_THROW(timeFromText32("20000229120000"));
+ EXPECT_NO_THROW(timeFromText32("20120229120000"));
+
+ // unusual case: this implementation allows SS=60 for "leap seconds"
+ EXPECT_NO_THROW(timeFromText32("20110101120060"));
+
+ // Out of range parameters
+ EXPECT_THROW(timeFromText32("19100223214617"), InvalidTime); // YY<1970
+ EXPECT_THROW(timeFromText32("20110001120000"), InvalidTime); // MM=00
+ EXPECT_THROW(timeFromText32("20111301120000"), InvalidTime); // MM=13
+ EXPECT_THROW(timeFromText32("20110100120000"), InvalidTime); // DD=00
+ EXPECT_THROW(timeFromText32("20110132120000"), InvalidTime); // DD=32
+ EXPECT_THROW(timeFromText32("20110431120000"), InvalidTime); // 'Apr31'
+ EXPECT_THROW(timeFromText32("20110101250000"), InvalidTime); // HH=25
+ EXPECT_THROW(timeFromText32("20110101126000"), InvalidTime); // mm=60
+ EXPECT_THROW(timeFromText32("20110101120061"), InvalidTime); // SS=61
+
+ // Feb 7, 06:28:15 UTC 2106 is the possible maximum time that can be
+ // represented as an unsigned 32bit integer without overflow.
+ EXPECT_EQ(4294967295LU, timeFromText32("21060207062815"));
+
+ // After that, timeFromText32() should start returning the second count
+ // modulo 2^32.
+ EXPECT_EQ(0, timeFromText32("21060207062816"));
+ EXPECT_EQ(10, timeFromText32("21060207062826"));
+
+ // On the other hand, the 64-bit version should return monotonically
+ // increasing counters.
+ EXPECT_EQ(4294967296LL, timeFromText64("21060207062816"));
+ EXPECT_EQ(4294967306LL, timeFromText64("21060207062826"));
+}
+
+// This helper templated function tells timeToText32 a faked current time.
+// The template parameter is that faked time in the form of int64_t seconds
+// since epoch.
+template <int64_t NOW>
+int64_t
+testGetTime() {
+ return (NOW);
+}
+
+// Seconds since epoch for the year 10K eve. Commonly used in some tests
+// below.
+const uint64_t YEAR10K_EVE = 253402300799LL;
+
+TEST_F(DNSSECTimeTest, toText) {
+ // Check a basic case with the default (normal) gettimeFunction
+ // based on the "real current time".
+ // Note: this will fail after year 2078, but at that point we won't use
+ // this program anyway:-)
+ EXPECT_EQ("20100311233000", timeToText32(1268350200));
+
+ // Set the current time to: Feb 18 09:04:14 UTC 2012 (an arbitrary choice
+ // in the range of the first half of uint32 since epoch).
+ detail::gettimeFunction = testGetTime<1329555854LL>;
+
+ // Test the "year 2038" problem.
+ // Check the result of toText() for "INT_MIN" in int32_t. It's in the
+ // 68-year range from the faked current time, so the result should be
+ // in year 2038, instead of 1901.
+ EXPECT_EQ("20380119031408", timeToText64(0x80000000L));
+ EXPECT_EQ("20380119031408", timeToText32(0x80000000L));
+
+ // A controversial case: what should we do with "-1"? It's out of range
+ // in future, but according to RFC time before epoch doesn't seem to be
+ // considered "in-range" either. Our toText() implementation handles
+ // this range as a special case and always treats them as future time
+ // until year 2038. This won't be a real issue in practice, though,
+ // since such too large values won't be used in actual deployment by then.
+ EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+ // After the singular point of year 2038, the first half of uint32 can
+ // point to a future time.
+ // Set the current time to: Apr 1 00:00:00 UTC 2038:
+ detail::gettimeFunction = testGetTime<2153692800LL>;
+ // then time "10" is Feb 7 06:28:26 UTC 2106
+ EXPECT_EQ("21060207062826", timeToText32(10));
+ // in 64-bit, it's 2^32 + 10
+ EXPECT_EQ("21060207062826", timeToText64(0x10000000aLL));
+
+ // After year 2106, the upper half of uint32 can point to past time
+ // (as it should).
+ detail::gettimeFunction = testGetTime<0x10000000aLL>;
+ EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+ // Try very large time value. Actually it's the possible farthest time
+ // that can be represented in the form of YYYYMMDDHHmmSS.
+ EXPECT_EQ("99991231235959", timeToText64(YEAR10K_EVE));
+ detail::gettimeFunction = testGetTime<YEAR10K_EVE - 10>;
+ EXPECT_EQ("99991231235959", timeToText32(4294197631LU));
+}
+
+TEST_F(DNSSECTimeTest, overflow) {
+ // Jan 1, Year 10,000.
+ EXPECT_THROW(timeToText64(253402300800LL), InvalidTime);
+ detail::gettimeFunction = testGetTime<YEAR10K_EVE - 10>;
+ EXPECT_THROW(timeToText32(4294197632LU), InvalidTime);
+}
+
+}
diff --git a/src/lib/util/tests/triplet_unittest.cc b/src/lib/util/tests/triplet_unittest.cc
new file mode 100644
index 0000000..9736011
--- /dev/null
+++ b/src/lib/util/tests/triplet_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/triplet.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <stdint.h>
+
+using namespace isc::util;
+using namespace isc;
+
+namespace {
+
+// constructor validation
+TEST(TripletTest, constructor) {
+
+ const uint32_t min = 10;
+ const uint32_t value = 20;
+ const uint32_t max = 30;
+
+ Triplet<uint32_t> x(min, value, max);
+
+ EXPECT_EQ(min, x.getMin());
+ EXPECT_EQ(value, x.get());
+ EXPECT_EQ(max, x.getMax());
+ EXPECT_FALSE(x.unspecified());
+
+ // requested values below min should return allowed min value
+ EXPECT_EQ(min, x.get(min - 5));
+
+ EXPECT_EQ(min, x.get(min));
+
+ // requesting a value from within the range (min < x < max) should
+ // return the requested value
+ EXPECT_EQ(17, x.get(17));
+
+ EXPECT_EQ(max, x.get(max));
+
+ EXPECT_EQ(max, x.get(max + 5));
+
+ // this will be boring. It is expected to return 42 no matter what
+ Triplet<uint32_t> y(42);
+
+ EXPECT_EQ(42, y.getMin()); // min, default and max are equal to 42
+ EXPECT_EQ(42, y.get()); // it returns ...
+ EXPECT_EQ(42, y.getMax()); // the exact value...
+ EXPECT_FALSE(x.unspecified());
+
+ // requested values below or above are ignore
+ EXPECT_EQ(42, y.get(5)); // all...
+ EXPECT_EQ(42, y.get(42)); // the...
+ EXPECT_EQ(42, y.get(80)); // time!
+}
+
+TEST(TripletTest, unspecified) {
+ Triplet<uint32_t> x;
+ // When using the constructor without parameters, the triplet
+ // value is unspecified.
+ EXPECT_EQ(0, x.getMin());
+ EXPECT_EQ(0, x.get());
+ EXPECT_EQ(0, x.getMax());
+ EXPECT_TRUE(x.unspecified());
+
+ // For the triplet which has unspecified value we can call accessors
+ // without an exception.
+ uint32_t exp_unspec = 0;
+ EXPECT_EQ(exp_unspec, x);
+
+ x = 72;
+ // Check if the new value has been assigned.
+ EXPECT_EQ(72, x.getMin());
+ EXPECT_EQ(72, x.get());
+ EXPECT_EQ(72, x.getMax());
+ // Triplet is now specified.
+ EXPECT_FALSE(x.unspecified());
+}
+
+// Triplets must be easy to use.
+// Simple to/from int conversions must be done on the fly.
+TEST(TripletTest, operator) {
+
+ uint32_t x = 47;
+
+ Triplet<uint32_t> foo(1,2,3);
+ Triplet<uint32_t> bar(4,5,6);
+
+ foo = bar;
+
+ EXPECT_EQ(4, foo.getMin());
+ EXPECT_EQ(5, foo.get());
+ EXPECT_EQ(6, foo.getMax());
+ EXPECT_FALSE(foo.unspecified());
+
+ // assignment operator: uint32_t => triplet
+ Triplet<uint32_t> y(0);
+ y = x;
+
+ EXPECT_EQ(x, y.get());
+
+ // let's try the other way around: triplet => uint32_t
+ uint32_t z = 0;
+ z = y;
+
+ EXPECT_EQ(x, z);
+}
+
+// check if specified values are sane
+TEST(TripletTest, sanityCheck) {
+
+ // min is larger than default
+ EXPECT_THROW(Triplet<uint32_t>(6,5,5), BadValue);
+
+ // max is smaller than default
+ EXPECT_THROW(Triplet<uint32_t>(5,5,4), BadValue);
+
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/util/tests/unlock_guard_unittests.cc b/src/lib/util/tests/unlock_guard_unittests.cc
new file mode 100644
index 0000000..65d868b
--- /dev/null
+++ b/src/lib/util/tests/unlock_guard_unittests.cc
@@ -0,0 +1,236 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <util/unlock_guard.h>
+#include <exceptions/exceptions.h>
+
+#include <mutex>
+#include <thread>
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief test mutex class used to check the internal state of a 'fictional'
+/// mutex so that the functionality of the UnlockGuard can be tested
+/// @note the test mutex can be recursive which means that a lock can be called
+/// on the same thread and not resulting in a dead lock
+class TestMutex {
+public:
+ /// @brief Constructor
+ ///
+ /// @param recursive sets the mutex as recursive mutex
+ TestMutex(bool recursive = false) : lock_(0), dead_lock_(false),
+ lock_count_(0), unlock_count_(0), recursive_(recursive) {
+ }
+
+ /// @brief lock the mutex
+ void lock() {
+ lock_guard<mutex> lk(mutex_);
+ if (lock_ >= 1) {
+ // mutex is already locked
+ if (!recursive_) {
+ // lock on a non-recursive mutex resulting in a dead lock
+ dead_lock_ = true;
+ isc_throw(isc::InvalidOperation,
+ "recursive lock on already locked mutex resulting in "
+ "dead lock");
+ } else {
+ // lock on a recursive mutex
+ if (this_thread::get_id() != id_) {
+ // lock on a recursive mutex on a different thread resulting
+ // in a dead lock
+ dead_lock_ = true;
+ isc_throw(isc::InvalidOperation,
+ "recursive lock on a different thread on already "
+ "locked mutex resulting in dead lock");
+ }
+ }
+ }
+ // increment the total number of locks
+ lock_count_++;
+ // increment the lock state
+ lock_++;
+ // save the thread id
+ id_ = this_thread::get_id();
+ }
+
+ /// @brief unlock the mutex
+ void unlock() {
+ lock_guard<mutex> lk(mutex_);
+ if (lock_ <= 0) {
+ // unlock an unlocked mutex
+ isc_throw(isc::InvalidOperation, "unlock on non locked mutex "
+ "resulting in undefined behavior");
+ }
+ if (lock_ == 1) {
+ // only one thread has the lock
+ // self healing mutex resetting the dead lock flag
+ dead_lock_ = false;
+ // reset the thread id
+ id_ = std::thread::id();
+ }
+ // increment the total number of unlocks
+ unlock_count_++;
+ // decrement the lock state
+ lock_--;
+ }
+
+ /// @brief get the mutex lock state
+ ///
+ /// @return the mutex lock state
+ int32_t getLock() {
+ lock_guard<mutex> lk(mutex_);
+ return lock_;
+ }
+
+ /// @brief get the mutex dead lock state
+ ///
+ /// @return the mutex dead lock state
+ bool getDeadLock() {
+ lock_guard<mutex> lk(mutex_);
+ return dead_lock_;
+ }
+
+ /// @brief get the number of locks performed on mutex
+ ///
+ /// @return the mutex number of locks
+ uint32_t getLockCount() {
+ lock_guard<mutex> lk(mutex_);
+ return lock_count_;
+ }
+
+ /// @brief get the number of unlocks performed on mutex
+ ///
+ /// @return the mutex number of unlocks
+ uint32_t getUnlockCount() {
+ lock_guard<mutex> lk(mutex_);
+ return unlock_count_;
+ }
+
+ /// @brief test the internal state of the mutex
+ ///
+ /// @param expected_lock check equality of this value with lock state
+ /// @param expected_lock_count check equality of this value with lock count
+ /// @param expected_unlock_count check equality of this value with unlock count
+ /// @param expected_dead_lock check equality of this value with dead lock state
+ void testMutexState(int32_t expected_lock,
+ uint32_t expected_lock_count,
+ uint32_t expected_unlock_count,
+ bool expected_dead_lock) {
+ ASSERT_EQ(getLock(), expected_lock);
+ ASSERT_EQ(getLockCount(), expected_lock_count);
+ ASSERT_EQ(getUnlockCount(), expected_unlock_count);
+ ASSERT_EQ(getDeadLock(), expected_dead_lock);
+ }
+
+private:
+ /// @brief internal lock state of the mutex
+ int32_t lock_;
+
+ /// @brief state which indicates that the mutex is in dead lock
+ bool dead_lock_;
+
+ /// @brief total number of locks performed on the mutex
+ uint32_t lock_count_;
+
+ /// @brief total number of unlocks performed on the mutex
+ uint32_t unlock_count_;
+
+ /// @brief flag to indicate if the mutex is recursive or not
+ bool recursive_;
+
+ /// @brief mutex used to keep the internal state consistent
+ mutex mutex_;
+
+ /// @brief the id of the thread holding the mutex
+ std::thread::id id_;
+};
+
+/// @brief Test Fixture for testing isc::util::UnlockGuard
+class UnlockGuardTest : public ::testing::Test {
+};
+
+/// @brief test TestMutex functionality with non-recursive mutex, and recursive
+/// mutex
+TEST_F(UnlockGuardTest, testMutex) {
+ shared_ptr<TestMutex> test_mutex;
+ // test non-recursive lock
+ test_mutex = make_shared<TestMutex>();
+ test_mutex->testMutexState(0, 0, 0, false);
+ {
+ // call lock_guard constructor which locks mutex
+ lock_guard<TestMutex> lock(*test_mutex.get());
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
+ test_mutex->testMutexState(1, 1, 0, false);
+ {
+ // call lock_guard constructor which locks mutex resulting in an
+ // exception as the mutex is already locked (dead lock)
+ EXPECT_THROW(lock_guard<TestMutex> lock(*test_mutex.get()),
+ isc::InvalidOperation);
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock true
+ // you should not be able to get here...using a real mutex
+ test_mutex->testMutexState(1, 1, 0, true);
+ }
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock true
+ // you should not be able to get here...using a real mutex
+ test_mutex->testMutexState(1, 1, 0, true);
+ }
+ // expect lock 0 lock_count 1 unlock_count 1 dead_lock false
+ // the implementation is self healing when completely unlocking the mutex
+ test_mutex->testMutexState(0, 1, 1, false);
+ // test recursive lock
+ test_mutex = make_shared<TestMutex>(true);
+ test_mutex->testMutexState(0, 0, 0, false);
+ {
+ // call lock_guard constructor which locks mutex
+ lock_guard<TestMutex> lock(*test_mutex.get());
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
+ test_mutex->testMutexState(1, 1, 0, false);
+ {
+ // call lock_guard constructor which locks mutex but does not block
+ // as this is done on the same thread and the mutex is recursive
+ EXPECT_NO_THROW(lock_guard<TestMutex> lock(*test_mutex.get()));
+ // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
+ // the destructor was already called in EXPECT_NO_THROW scope
+ test_mutex->testMutexState(1, 2, 1, false);
+ }
+ // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
+ test_mutex->testMutexState(1, 2, 1, false);
+ }
+ // expect lock 0 lock_count 2 unlock_count 2 dead_lock false
+ test_mutex->testMutexState(0, 2, 2, false);
+}
+
+/// @brief test UnlockGuard functionality with non-recursive mutex
+TEST_F(UnlockGuardTest, testUnlockGuard) {
+ shared_ptr<TestMutex> test_mutex;
+ // test non-recursive lock
+ test_mutex = make_shared<TestMutex>();
+ test_mutex->testMutexState(0, 0, 0, false);
+ {
+ // call lock_guard constructor which locks mutex
+ lock_guard<TestMutex> lock(*test_mutex.get());
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
+ test_mutex->testMutexState(1, 1, 0, false);
+ {
+ UnlockGuard<TestMutex> unlock_guard(*test_mutex.get());
+ // expect lock 0 lock_count 1 unlock_count 1 dead_lock false
+ test_mutex->testMutexState(0, 1, 1, false);
+ }
+ // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
+ test_mutex->testMutexState(1, 2, 1, false);
+ }
+ // expect lock 0 lock_count 2 unlock_count 2 dead_lock false
+ test_mutex->testMutexState(0, 2, 2, false);
+}
+
+} // namespace
diff --git a/src/lib/util/tests/utf8_unittest.cc b/src/lib/util/tests/utf8_unittest.cc
new file mode 100644
index 0000000..168f38b
--- /dev/null
+++ b/src/lib/util/tests/utf8_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/encode/utf8.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace std;
+
+namespace {
+
+// Verify it does nothing for ASCII.
+TEST(Utf8Test, foobar) {
+ string str("foobar");
+ vector<uint8_t> vec8 = encodeUtf8(str);
+ ASSERT_FALSE(vec8.empty());
+ const char* start = reinterpret_cast<const char*>(&vec8[0]);
+ string str8(start, start + vec8.size());
+ EXPECT_EQ(str, str8);
+}
+
+// Verify it encodes not ASCII as expected.
+TEST(Utf8Test, eightc) {
+ string str("-\x8c-");
+ vector<uint8_t> vec8 = encodeUtf8(str);
+ ASSERT_FALSE(vec8.empty());
+ const char* start = reinterpret_cast<const char*>(&vec8[0]);
+ string str8(start, start + vec8.size());
+ string expected("-\xc2\x8c-");
+ EXPECT_EQ(expected, str8);
+}
+
+// Verify it handles correctly control characters.
+TEST(Utf8Test, control) {
+ string str("fo\x00\n\bar");
+ vector<uint8_t> vec8 = encodeUtf8(str);
+ ASSERT_FALSE(vec8.empty());
+ const char* start = reinterpret_cast<const char*>(&vec8[0]);
+ string str8(start, start + vec8.size());
+ EXPECT_EQ(str, str8);
+}
+
+}
diff --git a/src/lib/util/tests/versioned_csv_file_unittest.cc b/src/lib/util/tests/versioned_csv_file_unittest.cc
new file mode 100644
index 0000000..36a1f91
--- /dev/null
+++ b/src/lib/util/tests/versioned_csv_file_unittest.cc
@@ -0,0 +1,501 @@
+// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <util/versioned_csv_file.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+namespace {
+
+using namespace isc::util;
+
+/// @brief Test fixture class for testing operations on VersionedCSVFile.
+///
+/// It implements basic operations on files, such as reading writing
+/// file removal and checking presence of the file. This is used by
+/// unit tests to verify correctness of the file created by the
+/// CSVFile class.
+class VersionedCSVFileTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets the path to the CSV file used throughout the tests.
+ /// The name of the file is test.csv and it is located in the
+ /// current build folder.
+ ///
+ /// It also deletes any dangling files after previous tests.
+ VersionedCSVFileTest();
+
+ /// @brief Destructor.
+ ///
+ /// Deletes the test CSV file if any.
+ virtual ~VersionedCSVFileTest();
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Check if test file exists on disk.
+ bool exists() const;
+
+ /// @brief Reads whole CSV file.
+ ///
+ /// @return Contents of the file.
+ std::string readFile() const;
+
+ /// @brief Removes existing file (if any).
+ int removeFile() const;
+
+ /// @brief Creates file with contents.
+ ///
+ /// @param contents Contents of the file.
+ void writeFile(const std::string& contents) const;
+
+ /// @brief Absolute path to the file used in the tests.
+ std::string testfile_;
+
+};
+
+VersionedCSVFileTest::VersionedCSVFileTest()
+ : testfile_(absolutePath("test.csv")) {
+ static_cast<void>(removeFile());
+}
+
+VersionedCSVFileTest::~VersionedCSVFileTest() {
+ static_cast<void>(removeFile());
+}
+
+std::string
+VersionedCSVFileTest::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << TEST_DATA_BUILDDIR << "/" << filename;
+ return (s.str());
+}
+
+bool
+VersionedCSVFileTest::exists() const {
+ std::ifstream fs(testfile_.c_str());
+ bool ok = fs.good();
+ fs.close();
+ return (ok);
+}
+
+std::string
+VersionedCSVFileTest::readFile() const {
+ std::ifstream fs(testfile_.c_str());
+ if (!fs.is_open()) {
+ return ("");
+ }
+ std::string contents((std::istreambuf_iterator<char>(fs)),
+ std::istreambuf_iterator<char>());
+ fs.close();
+ return (contents);
+}
+
+int
+VersionedCSVFileTest::removeFile() const {
+ return (remove(testfile_.c_str()));
+}
+
+void
+VersionedCSVFileTest::writeFile(const std::string& contents) const {
+ std::ofstream fs(testfile_.c_str(), std::ofstream::out);
+ if (fs.is_open()) {
+ fs << contents;
+ fs.close();
+ }
+}
+
+// This test checks that the function which is used to add columns of the
+// CSV file works as expected.
+TEST_F(VersionedCSVFileTest, addColumn) {
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+
+ // Verify that we're not allowed to open it without the schema
+ ASSERT_THROW(csv->open(), VersionedCSVFileError);
+
+ // Add two columns.
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+
+ // Make sure we can't add duplicates.
+ EXPECT_THROW(csv->addColumn("animal", "1.0", ""), CSVFileError);
+ EXPECT_THROW(csv->addColumn("color", "2.0", "blue"), CSVFileError);
+
+ // But we should still be able to add unique columns.
+ EXPECT_NO_THROW(csv->addColumn("age", "3.0", "21"));
+
+ // Assert that the file is opened, because the rest of the test relies
+ // on this.
+ ASSERT_NO_THROW(csv->recreate());
+ ASSERT_TRUE(exists());
+
+ // We should have 3 defined columns
+ // Input Header should match defined columns on new files
+ // Valid columns should match defined columns on new files
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(3, csv->getColumnCount());
+ EXPECT_EQ(3, csv->getInputHeaderCount());
+ EXPECT_EQ(3, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Schema versions for new files should always match
+ EXPECT_EQ("3.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("3.0", csv->getSchemaVersion());
+
+ // Input Schema State should be current for new files
+ EXPECT_EQ(VersionedCSVFile::CURRENT, csv->getInputSchemaState());
+ EXPECT_FALSE(csv->needsConversion());
+
+ // Make sure we can't add columns (even unique) when the file is open.
+ ASSERT_THROW(csv->addColumn("zoo", "3.0", ""), CSVFileError);
+
+ // Close the file.
+ ASSERT_NO_THROW(csv->close());
+ // And check that now it is possible to add the column.
+ EXPECT_NO_THROW(csv->addColumn("zoo", "3.0", ""));
+}
+
+// Verifies that a current schema version file loads correctly.
+TEST_F(VersionedCSVFileTest, currentSchemaTest) {
+
+ // Create our versioned file, with three columns
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "2.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "grey"));
+ ASSERT_NO_THROW(csv->addColumn("age", "2.0", "0"));
+
+ // Write a file compliant with the current schema version.
+ writeFile("animal,color,age\n"
+ "cat,black,2\n"
+ "lion,yellow,17\n"
+ "dog,brown,5\n");
+
+ // Header should pass validation and allow the open to succeed.
+ ASSERT_NO_THROW(csv->open());
+
+ // For schema current file We should have:
+ // 3 defined columns
+ // 3 columns total found in the header
+ // 3 valid columns found in the header
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(3, csv->getColumnCount());
+ EXPECT_EQ(3, csv->getInputHeaderCount());
+ EXPECT_EQ(3, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Input schema and current schema should both be 2.0
+ EXPECT_EQ("2.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("2.0", csv->getSchemaVersion());
+
+ // Input Schema State should be CURRENT
+ EXPECT_EQ(VersionedCSVFile::CURRENT, csv->getInputSchemaState());
+ EXPECT_FALSE(csv->needsConversion());
+
+ // First row is correct.
+ CSVRow row;
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("black", row.readAt(1));
+ EXPECT_EQ("2", row.readAt(2));
+
+ // Second row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("yellow", row.readAt(1));
+ EXPECT_EQ("17", row.readAt(2));
+
+ // Third row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("dog", row.readAt(0));
+ EXPECT_EQ("brown", row.readAt(1));
+ EXPECT_EQ("5", row.readAt(2));
+}
+
+
+// Verifies the basic ability to upgrade valid files.
+// It starts with a version 1.0 file and updates
+// it through two schema evolutions.
+TEST_F(VersionedCSVFileTest, upgradeOlderVersions) {
+
+ // Create version 1.0 schema CSV file
+ writeFile("animal\n"
+ "cat\n"
+ "lion\n"
+ "dog\n");
+
+ // Create our versioned file, with two columns, one for each
+ // schema version
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+
+ // Header should pass validation and allow the open to succeed.
+ ASSERT_NO_THROW(csv->open());
+
+ // We should have:
+ // 2 defined columns
+ // 1 column found in the header
+ // 1 valid column in the header
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(2, csv->getColumnCount());
+ EXPECT_EQ(1, csv->getInputHeaderCount());
+ EXPECT_EQ(1, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Input schema should be 1.0, while our current schema should be 2.0
+ EXPECT_EQ("1.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("2.0", csv->getSchemaVersion());
+
+ // Input Schema State should be NEEDS_UPGRADE
+ EXPECT_EQ(VersionedCSVFile::NEEDS_UPGRADE, csv->getInputSchemaState());
+ EXPECT_TRUE(csv->needsConversion());
+
+ // First row is correct.
+ CSVRow row;
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+
+ // Second row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+
+ // Third row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("dog", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+
+ // Now, let's try to append something to this file.
+ CSVRow row_write(2);
+ row_write.writeAt(0, "bird");
+ row_write.writeAt(1, "yellow");
+ ASSERT_NO_THROW(csv->append(row_write));
+
+ // Close the file
+ ASSERT_NO_THROW(csv->flush());
+ ASSERT_NO_THROW(csv->close());
+
+
+ // Check the file contents are correct.
+ EXPECT_EQ("animal\n"
+ "cat\n"
+ "lion\n"
+ "dog\n"
+ "bird,yellow\n",
+ readFile());
+
+ // Create a third schema by adding a column
+ ASSERT_NO_THROW(csv->addColumn("age", "3.0", "21"));
+ ASSERT_EQ(3, csv->getColumnCount());
+
+ // Header should pass validation and allow the open to succeed
+ ASSERT_NO_THROW(csv->open());
+
+ // We should have:
+ // 3 defined columns
+ // 1 column found in the header
+ // 1 valid column in the header
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(3, csv->getColumnCount());
+ EXPECT_EQ(1, csv->getInputHeaderCount());
+ EXPECT_EQ(1, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Make sure schema versions are accurate
+ EXPECT_EQ("1.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("3.0", csv->getSchemaVersion());
+
+ // Input Schema State should be NEEDS_UPGRADE
+ EXPECT_EQ(VersionedCSVFile::NEEDS_UPGRADE, csv->getInputSchemaState());
+ EXPECT_TRUE(csv->needsConversion());
+
+ // First row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ // Second row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ // Third row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("dog", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ // Fourth row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("bird", row.readAt(0));
+ EXPECT_EQ("yellow", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+}
+
+TEST_F(VersionedCSVFileTest, minimumValidColumn) {
+ // Create version 1.0 schema CSV file
+ writeFile("animal\n"
+ "cat\n"
+ "lion\n"
+ "dog\n");
+
+ // Create our versioned file, with three columns, one for each
+ // schema version
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+ ASSERT_NO_THROW(csv->addColumn("age", "3.0", "21"));
+
+ // Verify we can't set minimum columns with a non-existent column
+ EXPECT_THROW(csv->setMinimumValidColumns("bogus"), VersionedCSVFileError);
+
+ // Set the minimum number of columns to "color"
+ csv->setMinimumValidColumns("color");
+ EXPECT_EQ(2, csv->getMinimumValidColumns());
+
+ // Header validation should fail, too few columns
+ ASSERT_THROW(csv->open(), CSVFileError);
+
+ // Set the minimum number of columns to 1. File should parse now.
+ csv->setMinimumValidColumns("animal");
+ EXPECT_EQ(1, csv->getMinimumValidColumns());
+ ASSERT_NO_THROW(csv->open());
+
+ // First row is correct.
+ CSVRow row;
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("dog", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+}
+
+TEST_F(VersionedCSVFileTest, invalidHeaderColumn) {
+
+ // Create our version 2.0 schema file
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+
+ // Create a file with the correct number of columns but a wrong column name
+ writeFile("animal,colour\n"
+ "cat,red\n"
+ "lion,green\n");
+
+ // Header validation should fail, we have an invalid column
+ ASSERT_THROW(csv->open(), CSVFileError);
+}
+
+TEST_F(VersionedCSVFileTest, downGrading) {
+ // Create our version 2.0 schema file
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+
+ // Create schema 2.0 file PLUS an extra column
+ writeFile("animal,color,age\n"
+ "cat,red,5\n"
+ "lion,green,8\n");
+
+ // Header should validate and file should open.
+ ASSERT_NO_THROW(csv->open());
+
+ // We should have:
+ // 2 defined columns
+ // 3 columns found in the header
+ // 2 valid columns in the header
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(2, csv->getColumnCount());
+ EXPECT_EQ(3, csv->getInputHeaderCount());
+ EXPECT_EQ(2, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Input schema and current schema should both be 2.0
+ EXPECT_EQ("2.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("2.0", csv->getSchemaVersion());
+
+ // Input Schema State should be NEEDS_DOWNGRADE
+ EXPECT_EQ(VersionedCSVFile::NEEDS_DOWNGRADE, csv->getInputSchemaState());
+ EXPECT_TRUE(csv->needsConversion());
+
+ // First row is correct.
+ CSVRow row;
+ EXPECT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("red", row.readAt(1));
+
+ // No data beyond the second column
+ EXPECT_THROW(row.readAt(2), CSVFileError);
+
+ // Second row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("green", row.readAt(1));
+
+ // No data beyond the second column
+ EXPECT_THROW(row.readAt(2), CSVFileError);
+}
+
+
+TEST_F(VersionedCSVFileTest, rowChecking) {
+ // Create version 2.0 schema CSV file with a
+ // - valid header
+ // - row 0 has too many values
+ // - row 1 is valid
+ // - row 3 is too few values
+ writeFile("animal,color\n"
+ "cat,red,bogus_row_value\n"
+ "lion,green\n"
+ "too_few\n");
+
+ // Create our versioned file, with two columns, one for each
+ // schema version
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ csv->addColumn("animal", "1.0", "");
+ csv->addColumn("color", "2.0", "blue");
+
+ // Header validation should pass, so we can open
+ ASSERT_NO_THROW(csv->open());
+
+ CSVRow row;
+ // First row has too many
+ EXPECT_FALSE(csv->next(row));
+
+ // Second row is valid
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("green", row.readAt(1));
+
+ // Third row has too few
+ EXPECT_FALSE(csv->next(row));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/watch_socket_unittests.cc b/src/lib/util/tests/watch_socket_unittests.cc
new file mode 100644
index 0000000..b503844
--- /dev/null
+++ b/src/lib/util/tests/watch_socket_unittests.cc
@@ -0,0 +1,263 @@
+// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <config.h>
+#include <util/watch_socket.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/select.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_SYS_FILIO_H
+// FIONREAD is here on Solaris
+#include <sys/filio.h>
+#endif
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Returns the result of select() given an fd to check for read status.
+///
+/// @param fd_to_check The file descriptor to test
+///
+/// @return Returns less than one on an error, 0 if the fd is not ready to
+/// read, > 0 if it is ready to read.
+int selectCheck(int fd_to_check) {
+ fd_set read_fds;
+ int maxfd = 0;
+
+ FD_ZERO(&read_fds);
+
+ // Add this socket to listening set
+ FD_SET(fd_to_check, &read_fds);
+ maxfd = fd_to_check;
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 0;
+
+ return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+}
+
+/// @brief Tests the basic functionality of WatchSocket.
+TEST(WatchSocketTest, basics) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ EXPECT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ /// Verify that isReady() is false and that a call to select agrees.
+ EXPECT_FALSE(watch->isReady());
+ EXPECT_EQ(0, selectCheck(select_fd));
+
+ /// Verify that the socket can be marked ready.
+ ASSERT_NO_THROW(watch->markReady());
+
+ /// Verify that we have exactly one marker waiting to be read.
+ int count = 0;
+ EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+ EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+ /// Verify that we can call markReady again without error.
+ ASSERT_NO_THROW(watch->markReady());
+
+ /// Verify that we STILL have exactly one marker waiting to be read.
+ EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+ EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+ /// Verify that isReady() is true and that a call to select agrees.
+ EXPECT_TRUE(watch->isReady());
+ EXPECT_EQ(1, selectCheck(select_fd));
+
+ /// Verify that the socket can be cleared.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ /// Verify that isReady() is false and that a call to select agrees.
+ EXPECT_FALSE(watch->isReady());
+ EXPECT_EQ(0, selectCheck(select_fd));
+}
+
+/// @brief Checks behavior when select_fd is closed externally while in the
+/// "cleared" state.
+TEST(WatchSocketTest, closedWhileClear) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ ASSERT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ // Verify that socket does not appear ready.
+ ASSERT_EQ(0, watch->isReady());
+
+ // Interfere by closing the fd.
+ ASSERT_EQ(0, close(select_fd));
+
+ // Verify that socket does not appear ready.
+ ASSERT_EQ(0, watch->isReady());
+
+ // Verify that clear does NOT throw.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ // Verify that trying to mark it fails.
+ ASSERT_THROW(watch->markReady(), WatchSocketError);
+
+ // Verify that clear does NOT throw.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ // Verify that getSelectFd() returns invalid socket.
+ ASSERT_EQ(WatchSocket::SOCKET_NOT_VALID, watch->getSelectFd());
+}
+
+/// @brief Checks behavior when select_fd has closed while in the "ready"
+/// state.
+TEST(WatchSocketTest, closedWhileReady) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ ASSERT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ /// Verify that the socket can be marked ready.
+ ASSERT_NO_THROW(watch->markReady());
+ EXPECT_EQ(1, selectCheck(select_fd));
+ EXPECT_TRUE(watch->isReady());
+
+ // Interfere by closing the fd.
+ ASSERT_EQ(0, close(select_fd));
+
+ // Verify that isReady() does not throw.
+ ASSERT_NO_THROW(watch->isReady());
+
+ // and return false.
+ EXPECT_FALSE(watch->isReady());
+
+ // Verify that trying to clear it does not throw.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ // Verify the select_fd fails as socket is invalid/closed.
+ EXPECT_EQ(-1, selectCheck(select_fd));
+
+ // Verify that subsequent attempts to mark it will fail.
+ ASSERT_THROW(watch->markReady(), WatchSocketError);
+}
+
+/// @brief Checks behavior when select_fd has been marked ready but then
+/// emptied by an external read.
+TEST(WatchSocketTest, emptyReadySelectFd) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ ASSERT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ /// Verify that the socket can be marked ready.
+ ASSERT_NO_THROW(watch->markReady());
+ EXPECT_TRUE(watch->isReady());
+ EXPECT_EQ(1, selectCheck(select_fd));
+
+ // Interfere by reading the fd. This should empty the read pipe.
+ uint32_t buf = 0;
+ ASSERT_EQ((read (select_fd, &buf, sizeof(buf))), sizeof(buf));
+ ASSERT_EQ(WatchSocket::MARKER, buf);
+
+ // Really nothing that can be done to protect against this, but let's
+ // make sure we aren't in a weird state.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ // Verify the select_fd does not fail.
+ EXPECT_FALSE(watch->isReady());
+ EXPECT_EQ(0, selectCheck(select_fd));
+
+ // Verify that getSelectFd() returns is still good.
+ ASSERT_EQ(select_fd, watch->getSelectFd());
+}
+
+/// @brief Checks behavior when select_fd has been marked ready but then
+/// contents have been "corrupted" by a partial read.
+TEST(WatchSocketTest, badReadOnClear) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ ASSERT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ /// Verify that the socket can be marked ready.
+ ASSERT_NO_THROW(watch->markReady());
+ EXPECT_TRUE(watch->isReady());
+ EXPECT_EQ(1, selectCheck(select_fd));
+
+ // Interfere by reading the fd. This should empty the read pipe.
+ uint32_t buf = 0;
+ ASSERT_EQ((read (select_fd, &buf, 1)), 1);
+ ASSERT_NE(WatchSocket::MARKER, buf);
+
+ // Really nothing that can be done to protect against this, but let's
+ // make sure we aren't in a weird state.
+ /// @todo maybe clear should never throw, log only
+ ASSERT_THROW(watch->clearReady(), WatchSocketError);
+
+ // Verify the select_fd does not evaluate to ready.
+ EXPECT_FALSE(watch->isReady());
+ EXPECT_NE(1, selectCheck(select_fd));
+
+ // Verify that getSelectFd() returns INVALID.
+ ASSERT_EQ(WatchSocket::SOCKET_NOT_VALID, watch->getSelectFd());
+
+ // Verify that subsequent attempt to mark it fails.
+ ASSERT_THROW(watch->markReady(), WatchSocketError);
+}
+
+/// @brief Checks if the socket can be explicitly closed.
+TEST(WatchSocketTest, explicitClose) {
+ WatchSocketPtr watch;
+
+ // Create new instance of the socket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ // Make sure it has been opened by checking that its descriptor
+ // is valid.
+ EXPECT_NE(watch->getSelectFd(), WatchSocket::SOCKET_NOT_VALID);
+
+ // Close the socket.
+ std::string error_string;
+ ASSERT_TRUE(watch->closeSocket(error_string));
+
+ // Make sure that the descriptor is now invalid which indicates
+ // that the socket has been closed.
+ EXPECT_EQ(WatchSocket::SOCKET_NOT_VALID, watch->getSelectFd());
+ // No errors should be reported.
+ EXPECT_TRUE(error_string.empty());
+ // Not ready too.
+ ASSERT_NO_THROW(watch->isReady());
+ EXPECT_FALSE(watch->isReady());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/watched_thread_unittest.cc b/src/lib/util/tests/watched_thread_unittest.cc
new file mode 100644
index 0000000..dd01550
--- /dev/null
+++ b/src/lib/util/tests/watched_thread_unittest.cc
@@ -0,0 +1,218 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/watched_thread.h>
+
+#include <gtest/gtest.h>
+
+#include <atomic>
+#include <functional>
+#include <signal.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test Fixture for testing @c isc::util::WatchedThread
+class WatchedThreadTest : public ::testing::Test {
+public:
+ /// @brief Maximum number of passes allowed in worker event loop
+ static const int WORKER_MAX_PASSES;
+
+ /// @brief Constructor.
+ WatchedThreadTest() {}
+
+ /// @brief Destructor.
+ ~WatchedThreadTest() {
+ }
+
+ /// @brief Sleeps for a given number of event periods sleep
+ /// Each period is 50 ms.
+ void nap(int periods) {
+ usleep(periods * 50 * 1000);
+ };
+
+ /// @brief Worker function to be used by the WatchedThread's thread
+ ///
+ /// The function runs 10 passes through an "event" loop.
+ /// On each pass:
+ /// - check terminate command
+ /// - instigate the desired event (second pass only)
+ /// - naps for 1 period (50ms)
+ ///
+ /// @param watch_type type of event that should occur
+ void worker(WatchedThread::WatchType watch_type) {
+ sigset_t nsset;
+ pthread_sigmask(SIG_SETMASK, 0, &nsset);
+ EXPECT_EQ(1, sigismember(&nsset, SIGCHLD));
+ EXPECT_EQ(1, sigismember(&nsset, SIGINT));
+ EXPECT_EQ(1, sigismember(&nsset, SIGHUP));
+ EXPECT_EQ(1, sigismember(&nsset, SIGTERM));
+ for (passes_ = 1; passes_ < WORKER_MAX_PASSES; ++passes_) {
+
+ // Stop if we're told to do it.
+ if (wthread_->shouldTerminate()) {
+ return;
+ }
+
+ // On the second pass, set the event.
+ if (passes_ == 2) {
+ switch (watch_type) {
+ case WatchedThread::ERROR:
+ wthread_->setError("we have an error");
+ break;
+ case WatchedThread::READY:
+ wthread_->markReady(watch_type);
+ break;
+ case WatchedThread::TERMINATE:
+ default:
+ // Do nothing, we're waiting to be told to stop.
+ break;
+ }
+ }
+
+ // Take a nap.
+ nap(1);
+ }
+
+ // Indicate why we stopped.
+ wthread_->setError("thread expired");
+ }
+
+ /// @brief Current WatchedThread instance.
+ WatchedThreadPtr wthread_;
+
+ /// @brief Counter used to track the number of passes made
+ /// within the thread worker function.
+ std::atomic<int> passes_;
+};
+
+const int WatchedThreadTest::WORKER_MAX_PASSES = 10;
+
+/// Verifies the basic operation of the WatchedThread class.
+/// It checks that a WatchedThread can be created, can be stopped,
+/// and that in set and clear sockets.
+TEST_F(WatchedThreadTest, watchedThreadClassBasics) {
+
+ /// We'll create a WatchedThread and let it run until it expires. (Note this is more
+ /// of a test of WatchedThreadTest itself and ensures that the assumptions made in
+ /// our other tests as to why threads have finished are sound.
+ wthread_.reset(new WatchedThread());
+ ASSERT_FALSE(wthread_->isRunning());
+ wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE));
+ ASSERT_TRUE(wthread_->isRunning());
+
+ // Wait more long enough (we hope) for the thread to expire.
+ nap(WORKER_MAX_PASSES * 4);
+
+ // It should have done the maximum number of passes.
+ EXPECT_EQ(passes_, WORKER_MAX_PASSES);
+
+ // Error should be ready and error text should be "thread expired".
+ ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+ EXPECT_EQ("thread expired", wthread_->getLastError());
+
+ // Thread is technically still running, so let's stop it.
+ EXPECT_TRUE(wthread_->isRunning());
+ ASSERT_NO_THROW(wthread_->stop());
+ ASSERT_FALSE(wthread_->isRunning());
+
+ /// Now we'll test stopping a thread.
+ /// Start the WatchedThread, let it run a little and then tell it to stop.
+ wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE));
+ ASSERT_TRUE(wthread_->isRunning());
+
+ // No watches should be ready.
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+
+ // Wait a little while.
+ nap(3);
+
+ // Tell it to stop.
+ wthread_->stop();
+ ASSERT_FALSE(wthread_->isRunning());
+
+ // It should have done less than the maximum number of passes.
+ EXPECT_LT(passes_, WORKER_MAX_PASSES);
+
+ // No watches should be ready. Error text should be "thread stopped".
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+ EXPECT_EQ("thread stopped", wthread_->getLastError());
+
+
+ // Next we'll test error notification.
+ // Start the WatchedThread with a thread that sets an error on the second pass.
+ wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::ERROR));
+ ASSERT_TRUE(wthread_->isRunning());
+
+ // No watches should be ready.
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+
+ // Wait a little while.
+ nap(6);
+
+ // It should now indicate an error.
+ ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR));
+ EXPECT_EQ("we have an error", wthread_->getLastError());
+
+ // Tell it to stop.
+ wthread_->stop();
+ ASSERT_FALSE(wthread_->isRunning());
+
+ // It should have done less than the maximum number of passes.
+ EXPECT_LT(passes_, WORKER_MAX_PASSES);
+
+ // No watches should be ready. Error text should be "thread stopped".
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+ EXPECT_EQ("thread stopped", wthread_->getLastError());
+
+
+ // Finally, we'll test data ready notification.
+ // We'll start the WatchedThread with a thread that indicates data ready on its second pass.
+ wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::READY));
+ ASSERT_TRUE(wthread_->isRunning());
+
+ // No watches should be ready.
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+
+ // Wait a little while.
+ nap(6);
+
+ // It should now indicate data ready.
+ ASSERT_TRUE(wthread_->isReady(WatchedThread::READY));
+
+ // Tell it to stop.
+ wthread_->stop();
+ ASSERT_FALSE(wthread_->isRunning());
+
+ // It should have done less than the maximum number of passes.
+ EXPECT_LT(passes_, WORKER_MAX_PASSES);
+
+ // No watches should be ready. Error text should be "thread stopped".
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+ EXPECT_EQ("thread stopped", wthread_->getLastError());
+}
+
+}
diff --git a/src/lib/util/thread_pool.h b/src/lib/util/thread_pool.h
new file mode 100644
index 0000000..fdfce0f
--- /dev/null
+++ b/src/lib/util/thread_pool.h
@@ -0,0 +1,527 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef THREAD_POOL_H
+#define THREAD_POOL_H
+
+#include <exceptions/exceptions.h>
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <atomic>
+#include <chrono>
+#include <cmath>
+#include <condition_variable>
+#include <list>
+#include <mutex>
+#include <queue>
+#include <thread>
+
+#include <signal.h>
+
+namespace isc {
+namespace util {
+
+/// @brief Defines a thread pool which uses a thread pool queue for managing
+/// work items. Each work item is a 'functor' object.
+///
+/// @tparam WorkItem a functor
+/// @tparam Container a 'queue like' container
+template <typename WorkItem, typename Container = std::deque<boost::shared_ptr<WorkItem>>>
+struct ThreadPool {
+ /// @brief Rounding value for 10 packet statistic.
+ static const double CEXP10;
+
+ /// @brief Rounding value for 100 packet statistic.
+ static const double CEXP100;
+
+ /// @brief Rounding value for 1000 packet statistic.
+ static const double CEXP1000;
+
+ /// @brief Type of shared pointers to work items.
+ typedef typename boost::shared_ptr<WorkItem> WorkItemPtr;
+
+ /// @brief Constructor
+ ThreadPool() {
+ }
+
+ /// @brief Destructor
+ ~ThreadPool() {
+ reset();
+ }
+
+ /// @brief reset the thread pool stopping threads and clearing the internal
+ /// queue
+ ///
+ /// It can be called several times even when the thread pool is stopped
+ void reset() {
+ stopInternal();
+ queue_.clear();
+ }
+
+ /// @brief start all the threads
+ ///
+ /// @param thread_count specifies the number of threads to be created and
+ /// started
+ ///
+ /// @throw InvalidOperation if thread pool already started
+ /// @throw InvalidParameter if thread count is 0
+ void start(uint32_t thread_count) {
+ if (!thread_count) {
+ isc_throw(InvalidParameter, "thread count is 0");
+ }
+ if (queue_.enabled()) {
+ isc_throw(InvalidOperation, "thread pool already started");
+ }
+ startInternal(thread_count);
+ }
+
+ /// @brief stop all the threads
+ ///
+ /// @throw InvalidOperation if thread pool already stopped
+ void stop() {
+ if (!queue_.enabled()) {
+ isc_throw(InvalidOperation, "thread pool already stopped");
+ }
+ stopInternal();
+ }
+
+ /// @brief add a work item to the thread pool
+ ///
+ /// @param item the 'functor' object to be added to the queue
+ /// @return false if the queue was full and oldest item(s) was dropped,
+ /// true otherwise.
+ bool add(const WorkItemPtr& item) {
+ return (queue_.pushBack(item));
+ }
+
+ /// @brief add a work item to the thread pool at front
+ ///
+ /// @param item the 'functor' object to be added to the queue
+ /// @return false if the queue was full, true otherwise.
+ bool addFront(const WorkItemPtr& item) {
+ return (queue_.pushFront(item));
+ }
+
+ /// @brief count number of work items in the queue
+ ///
+ /// @return the number of work items in the queue
+ size_t count() {
+ return (queue_.count());
+ }
+
+ /// @brief wait for current items to be processed
+ ///
+ /// Used to block the calling thread until all items in the queue have
+ /// been processed
+ void wait() {
+ auto id = std::this_thread::get_id();
+ if (checkThreadId(id)) {
+ isc_throw(MultiThreadingInvalidOperation, "thread pool wait called by worker thread");
+ }
+ queue_.wait();
+ }
+
+ /// @brief wait for items to be processed or return after timeout
+ ///
+ /// Used to block the calling thread until all items in the queue have
+ /// been processed or return after timeout
+ ///
+ /// @param seconds the time in seconds to wait for tasks to finish
+ /// @return true if all tasks finished, false on timeout
+ bool wait(uint32_t seconds) {
+ auto id = std::this_thread::get_id();
+ if (checkThreadId(id)) {
+ isc_throw(MultiThreadingInvalidOperation, "thread pool wait with timeout called by worker thread");
+ }
+ return (queue_.wait(seconds));
+ }
+
+ /// @brief set maximum number of work items in the queue
+ ///
+ /// @param max_queue_size the maximum size (0 means unlimited)
+ void setMaxQueueSize(size_t max_queue_size) {
+ queue_.setMaxQueueSize(max_queue_size);
+ }
+
+ /// @brief get maximum number of work items in the queue
+ ///
+ /// @return the maximum size (0 means unlimited)
+ size_t getMaxQueueSize() {
+ return (queue_.getMaxQueueSize());
+ }
+
+ /// @brief size number of thread pool threads
+ ///
+ /// @return the number of threads
+ size_t size() {
+ return (threads_.size());
+ }
+
+ /// @brief get queue length statistic
+ ///
+ /// @param which select the statistic (10, 100 or 1000)
+ /// @return the queue length statistic
+ /// @throw InvalidParameter if which is not 10 and 100 and 1000.
+ double getQueueStat(size_t which) {
+ return (queue_.getQueueStat(which));
+ }
+
+private:
+ /// @brief start all the threads
+ ///
+ /// @param thread_count specifies the number of threads to be created and
+ /// started
+ void startInternal(uint32_t thread_count) {
+ // Protect us against signals
+ sigset_t sset;
+ sigset_t osset;
+ sigemptyset(&sset);
+ sigaddset(&sset, SIGCHLD);
+ sigaddset(&sset, SIGINT);
+ sigaddset(&sset, SIGHUP);
+ sigaddset(&sset, SIGTERM);
+ pthread_sigmask(SIG_BLOCK, &sset, &osset);
+ queue_.enable(thread_count);
+ try {
+ for (uint32_t i = 0; i < thread_count; ++i) {
+ threads_.push_back(boost::make_shared<std::thread>(&ThreadPool::run, this));
+ }
+ } catch (...) {
+ // Restore signal mask.
+ pthread_sigmask(SIG_SETMASK, &osset, 0);
+ throw;
+ }
+ // Restore signal mask.
+ pthread_sigmask(SIG_SETMASK, &osset, 0);
+ }
+
+ /// @brief stop all the threads
+ void stopInternal() {
+ auto id = std::this_thread::get_id();
+ if (checkThreadId(id)) {
+ isc_throw(MultiThreadingInvalidOperation, "thread pool stop called by worker thread");
+ }
+ queue_.disable();
+ for (auto thread : threads_) {
+ thread->join();
+ }
+ threads_.clear();
+ }
+
+ /// @brief check specified thread id against own threads
+ ///
+ /// @return true if thread is owned, false otherwise
+ bool checkThreadId(std::thread::id id) {
+ for (auto thread : threads_) {
+ if (id == thread->get_id()) {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+ /// @brief Defines a generic thread pool queue.
+ ///
+ /// The main purpose is to safely manage thread pool tasks.
+ /// The thread pool queue can be 'disabled', which means that no items can be
+ /// removed from the queue, or 'enabled', which guarantees that inserting or
+ /// removing items are thread safe.
+ /// In 'disabled' state, all threads waiting on the queue are unlocked and all
+ /// operations are non blocking.
+ ///
+ /// @tparam Item a 'smart pointer' to a functor
+ /// @tparam QueueContainer a 'queue like' container
+ template <typename Item, typename QueueContainer = std::queue<Item>>
+ struct ThreadPoolQueue {
+ /// @brief Constructor
+ ///
+ /// Creates the thread pool queue in 'disabled' state
+ ThreadPoolQueue()
+ : enabled_(false), max_queue_size_(0), working_(0),
+ stat10(0.), stat100(0.), stat1000(0.) {
+ }
+
+ /// @brief Destructor
+ ///
+ /// Destroys the thread pool queue
+ ~ThreadPoolQueue() {
+ disable();
+ clear();
+ }
+
+ /// @brief set maximum number of work items in the queue
+ ///
+ /// @return the maximum size (0 means unlimited)
+ void setMaxQueueSize(size_t max_queue_size) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ max_queue_size_ = max_queue_size;
+ }
+
+ /// @brief get maximum number of work items in the queue
+ ///
+ /// @return the maximum size (0 means unlimited)
+ size_t getMaxQueueSize() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (max_queue_size_);
+ }
+
+ /// @brief push work item to the queue
+ ///
+ /// Used to add work items to the queue.
+ /// When the queue is full oldest items are removed and false is
+ /// returned.
+ /// This function adds an item to the queue and wakes up at least one
+ /// thread waiting on the queue.
+ ///
+ /// @param item the new item to be added to the queue
+ /// @return false if the queue was full and oldest item(s) dropped,
+ /// true otherwise
+ bool pushBack(const Item& item) {
+ bool ret = true;
+ if (!item) {
+ return (ret);
+ }
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (max_queue_size_ != 0) {
+ while (queue_.size() >= max_queue_size_) {
+ queue_.pop_front();
+ ret = false;
+ }
+ }
+ queue_.push_back(item);
+ }
+ // Notify pop function so that it can effectively remove a work item.
+ cv_.notify_one();
+ return (ret);
+ }
+
+ /// @brief push work item to the queue at front.
+ ///
+ /// Used to add work items to the queue at front.
+ /// When the queue is full the item is not added.
+ ///
+ /// @param item the new item to be added to the queue
+ /// @return false if the queue was full, true otherwise
+ bool pushFront(const Item& item) {
+ if (!item) {
+ return (true);
+ }
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if ((max_queue_size_ != 0) &&
+ (queue_.size() >= max_queue_size_)) {
+ return (false);
+ }
+ queue_.push_front(item);
+ }
+ // Notify pop function so that it can effectively remove a work item.
+ cv_.notify_one();
+ return (true);
+ }
+
+ /// @brief pop work item from the queue or block waiting
+ ///
+ /// Used to retrieve and remove a work item from the queue
+ /// If the queue is 'disabled', this function returns immediately an empty
+ /// element.
+ /// If the queue is 'enabled', this function returns the first element in
+ /// the queue or blocks the calling thread if there are no work items
+ /// available.
+ /// Before a work item is returned statistics are updated.
+ ///
+ /// @return the first work item from the queue or an empty element.
+ Item pop() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ --working_;
+ // Wait for push or disable functions.
+ if (working_ == 0 && queue_.empty()) {
+ wait_cv_.notify_all();
+ }
+ cv_.wait(lock, [&]() {return (!enabled_ || !queue_.empty());});
+ if (!enabled_) {
+ return (Item());
+ }
+ ++working_;
+ size_t length = queue_.size();
+ stat10 = stat10 * CEXP10 + (1 - CEXP10) * length;
+ stat100 = stat100 * CEXP100 + (1 - CEXP100) * length;
+ stat1000 = stat1000 * CEXP1000 + (1 - CEXP1000) * length;
+ Item item = queue_.front();
+ queue_.pop_front();
+ return (item);
+ }
+
+ /// @brief count number of work items in the queue
+ ///
+ /// Returns the number of work items in the queue
+ ///
+ /// @return the number of work items
+ size_t count() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (queue_.size());
+ }
+
+ /// @brief wait for current items to be processed
+ ///
+ /// Used to block the calling thread until all items in the queue have
+ /// been processed
+ void wait() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ // Wait for any item or for working threads to finish.
+ wait_cv_.wait(lock, [&]() {return (working_ == 0 && queue_.empty());});
+ }
+
+ /// @brief wait for items to be processed or return after timeout
+ ///
+ /// Used to block the calling thread until all items in the queue have
+ /// been processed or return after timeout
+ ///
+ /// @param seconds the time in seconds to wait for tasks to finish
+ /// @return true if all tasks finished, false on timeout
+ bool wait(uint32_t seconds) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ // Wait for any item or for working threads to finish.
+ bool ret = wait_cv_.wait_for(lock, std::chrono::seconds(seconds),
+ [&]() {return (working_ == 0 && queue_.empty());});
+ return (ret);
+ }
+
+ /// @brief get queue length statistic
+ ///
+ /// @param which select the statistic (10, 100 or 1000)
+ /// @return the queue length statistic
+ /// @throw InvalidParameter if which is not 10 and 100 and 1000.
+ double getQueueStat(size_t which) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ switch (which) {
+ case 10:
+ return (stat10);
+ case 100:
+ return (stat100);
+ case 1000:
+ return (stat1000);
+ default:
+ isc_throw(InvalidParameter, "supported statistic for "
+ << "10/100/1000 only, not " << which);
+ }
+ }
+
+ /// @brief clear remove all work items
+ ///
+ /// Removes all queued work items
+ void clear() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ queue_ = QueueContainer();
+ working_ = 0;
+ wait_cv_.notify_all();
+ }
+
+ /// @brief enable the queue
+ ///
+ /// Sets the queue state to 'enabled'
+ ///
+ /// @param number of working threads
+ void enable(uint32_t thread_count) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ enabled_ = true;
+ working_ = thread_count;
+ }
+
+ /// @brief disable the queue
+ ///
+ /// Sets the queue state to 'disabled'
+ void disable() {
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ enabled_ = false;
+ }
+ // Notify pop so that it can exit.
+ cv_.notify_all();
+ }
+
+ /// @brief return the state of the queue
+ ///
+ /// Returns the state of the queue
+ ///
+ /// @return the state
+ bool enabled() {
+ return (enabled_);
+ }
+
+ private:
+ /// @brief underlying queue container
+ QueueContainer queue_;
+
+ /// @brief mutex used for critical sections
+ std::mutex mutex_;
+
+ /// @brief condition variable used to signal waiting threads
+ std::condition_variable cv_;
+
+ /// @brief condition variable used to wait for all items to be processed
+ std::condition_variable wait_cv_;
+
+ /// @brief the sate of the queue
+ /// The 'enabled' state corresponds to true value
+ /// The 'disabled' state corresponds to false value
+ std::atomic<bool> enabled_;
+
+ /// @brief maximum number of work items in the queue
+ /// (0 means unlimited)
+ size_t max_queue_size_;
+
+ /// @brief number of threads currently doing work
+ uint32_t working_;
+
+ /// @brief queue length statistic for 10 packets
+ double stat10;
+
+ /// @brief queue length statistic for 100 packets
+ double stat100;
+
+ /// @brief queue length statistic for 1000 packets
+ double stat1000;
+ };
+
+ /// @brief run function of each thread
+ void run() {
+ while (queue_.enabled()) {
+ WorkItemPtr item = queue_.pop();
+ if (item) {
+ try {
+ (*item)();
+ } catch (...) {
+ // catch all exceptions
+ }
+ }
+ }
+ }
+
+ /// @brief list of worker threads
+ std::vector<boost::shared_ptr<std::thread>> threads_;
+
+ /// @brief underlying work items queue
+ ThreadPoolQueue<WorkItemPtr, Container> queue_;
+};
+
+/// Initialize the 10 packet rounding to exp(-.1)
+template <typename W, typename C>
+const double ThreadPool<W, C>::CEXP10 = std::exp(-.1);
+
+/// Initialize the 100 packet rounding to exp(-.01)
+template <typename W, typename C>
+const double ThreadPool<W, C>::CEXP100 = std::exp(-.01);
+
+/// Initialize the 1000 packet rounding to exp(-.001)
+template <typename W, typename C>
+const double ThreadPool<W, C>::CEXP1000 = std::exp(-.001);
+
+} // namespace util
+} // namespace isc
+
+#endif // THREAD_POOL_H
diff --git a/src/lib/util/time_utilities.cc b/src/lib/util/time_utilities.cc
new file mode 100644
index 0000000..5da1db7
--- /dev/null
+++ b/src/lib/util/time_utilities.cc
@@ -0,0 +1,203 @@
+// Copyright (C) 2010-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <stdint.h>
+
+#include <sys/time.h>
+
+#include <string>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+#include <stdio.h>
+#include <time.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/time_utilities.h>
+
+using namespace std;
+
+namespace {
+int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+inline bool
+isLeap(const int y) {
+ return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0);
+}
+
+unsigned int
+yearSecs(const int year) {
+ return ((isLeap(year) ? 366 : 365 ) * 86400);
+}
+
+unsigned int
+monthSecs(const int month, const int year) {
+ return ((days[month] + ((month == 1 && isLeap(year)) ? 1 : 0 )) * 86400);
+}
+}
+
+namespace isc {
+namespace util {
+
+string
+timeToText64(uint64_t value) {
+ struct tm tm;
+ unsigned int secs;
+
+ // We cannot rely on gmtime() because time_t may not be of 64 bit
+ // integer. The following conversion logic is borrowed from BIND 9.
+ tm.tm_year = 70;
+ while ((secs = yearSecs(tm.tm_year + 1900)) <= value) {
+ value -= secs;
+ ++tm.tm_year;
+ if (tm.tm_year + 1900 > 9999) {
+ isc_throw(InvalidTime,
+ "Time value out of range (year > 9999): " <<
+ tm.tm_year + 1900);
+ }
+ }
+ tm.tm_mon = 0;
+ while ((secs = monthSecs(tm.tm_mon, tm.tm_year + 1900)) <= value) {
+ value -= secs;
+ tm.tm_mon++;
+ }
+ tm.tm_mday = 1;
+ while (86400 <= value) {
+ value -= 86400;
+ ++tm.tm_mday;
+ }
+ tm.tm_hour = 0;
+ while (3600 <= value) {
+ value -= 3600;
+ ++tm.tm_hour;
+ }
+ tm.tm_min = 0;
+ while (60 <= value) {
+ value -= 60;
+ ++tm.tm_min;
+ }
+ tm.tm_sec = value; // now t < 60, so this substitution is safe.
+
+ ostringstream oss;
+ oss << setfill('0')
+ << setw(4) << tm.tm_year + 1900
+ << setw(2) << tm.tm_mon + 1
+ << setw(2) << tm.tm_mday
+ << setw(2) << tm.tm_hour
+ << setw(2) << tm.tm_min
+ << setw(2) << tm.tm_sec;
+ return (oss.str());
+}
+
+// timeToText32() below uses the current system time. To test it with
+// unusual current time values we introduce the following function pointer;
+// when it's non NULL, we call it to get the (normally faked) current time.
+// Otherwise we use the standard gettimeofday(2). This hook is specifically
+// intended for testing purposes, so, even if it's visible outside of this
+// library, it's not even declared in a header file.
+namespace detail {
+int64_t (*gettimeFunction)() = NULL;
+
+int64_t
+gettimeWrapper() {
+ if (gettimeFunction != NULL) {
+ return (gettimeFunction());
+ }
+
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ return (static_cast<int64_t>(now.tv_sec));
+}
+}
+
+string
+timeToText32(const uint32_t value) {
+ // We first adjust the time to the closest epoch based on the current time.
+ // Note that the following variables must be signed in order to handle
+ // time until year 2038 correctly.
+ const int64_t start = detail::gettimeWrapper() - 0x7fffffff;
+ int64_t base = 0;
+ int64_t t;
+ while ((t = (base + value)) < start) {
+ base += 0x100000000LL;
+ }
+
+ // Then convert it to text.
+ return (timeToText64(t));
+}
+
+namespace {
+const size_t DATE_LEN = 14; // YYYYMMDDHHmmSS
+
+inline uint64_t ull(const int c) { return (static_cast<uint64_t>(c)); }
+
+inline void
+checkRange(const unsigned min, const unsigned max, const unsigned value,
+ const string& valname)
+{
+ if ((value >= min) && (value <= max)) {
+ return;
+ }
+ isc_throw(InvalidTime, "Invalid " << valname << " value: " << value);
+}
+}
+
+uint64_t
+timeFromText64(const string& time_txt) {
+ // Confirm the source only consists digits. sscanf() allows some
+ // minor exceptions.
+ for (string::size_type i = 0; i < time_txt.length(); ++i) {
+ if (!isdigit(time_txt.at(i))) {
+ isc_throw(InvalidTime, "Couldn't convert non-numeric time value: "
+ << time_txt);
+ }
+ }
+
+ unsigned year, month, day, hour, minute, second;
+ if (time_txt.length() != DATE_LEN ||
+ sscanf(time_txt.c_str(), "%4u%2u%2u%2u%2u%2u",
+ &year, &month, &day, &hour, &minute, &second) != 6)
+ {
+ isc_throw(InvalidTime, "Couldn't convert time value: " << time_txt);
+ }
+
+ checkRange(1970, 9999, year, "year");
+ checkRange(1, 12, month, "month");
+ checkRange(1, days[month - 1] + ((month == 2 && isLeap(year)) ? 1 : 0),
+ day, "day");
+ checkRange(0, 23, hour, "hour");
+ checkRange(0, 59, minute, "minute");
+ checkRange(0, 60, second, "second"); // 60 == leap second.
+
+ uint64_t timeval = second + (ull(60) * minute) + (ull(3600) * hour) +
+ ((day - 1) * ull(86400));
+ for (unsigned m = 0; m < (month - 1); ++m) {
+ timeval += days[m] * ull(86400);
+ }
+ if (isLeap(year) && month > 2) {
+ timeval += ull(86400);
+ }
+ for (unsigned y = 1970; y < year; ++y) {
+ timeval += ((isLeap(y) ? 366 : 365) * ull(86400));
+ }
+
+ return (timeval);
+}
+
+uint32_t
+timeFromText32(const string& time_txt) {
+ // The implicit conversion from uint64_t to uint32_t should just work here,
+ // because we only need to drop higher 32 bits.
+ return (timeFromText64(time_txt));
+}
+
+}
+}
diff --git a/src/lib/util/time_utilities.h b/src/lib/util/time_utilities.h
new file mode 100644
index 0000000..226a632
--- /dev/null
+++ b/src/lib/util/time_utilities.h
@@ -0,0 +1,165 @@
+// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TIME_UTILITIES_H
+#define TIME_UTILITIES_H 1
+
+#include <string>
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#include <exceptions/exceptions.h>
+
+//
+// Note: this helper module isn't specific to the DNS protocol per se.
+// We should probably move this to somewhere else, possibly in some common
+// utility area.
+//
+
+namespace isc {
+namespace util {
+
+///
+/// \brief A standard DNS (or ISC) module exception that is thrown if
+/// a time conversion function encounters bad input
+///
+class InvalidTime : public Exception {
+public:
+ InvalidTime(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+namespace detail {
+/// Return the current time in seconds
+///
+/// This function returns the "current" time in seconds from epoch
+/// (00:00:00 January 1, 1970) as a 64-bit signed integer. The return
+/// value can represent a point of time before epoch as a negative number.
+///
+/// This function is provided to help test time conscious implementations
+/// such as DNSSEC and TSIG signatures. It is difficult to test them with
+/// an unusual or a specifically chosen "current" via system-provided
+/// library functions to get time. This function acts as a straightforward
+/// wrapper of such a library function, but provides test code with a hook
+/// to return an arbitrary time value: if \c isc::util::detail::gettimeFunction
+/// is set to a pointer of function that returns 64-bit signed integer,
+/// \c gettimeWrapper() calls that function instead of the system library.
+///
+/// This hook variable is specifically intended for testing purposes, so,
+/// even if it's visible outside of this library, it's not even declared in a
+/// header file.
+///
+/// If the implementation doesn't need to be tested with faked current time,
+/// it should simply use the system supplied library function instead of
+/// this one.
+int64_t gettimeWrapper();
+}
+
+///
+/// \name DNSSEC time conversion functions.
+///
+/// These functions convert between times represented in seconds (in integer)
+/// since epoch and those in the textual form used in the RRSIG records.
+/// For integers we provide both 32-bit and 64-bit versions.
+/// The RRSIG expiration and inception fields are both 32-bit unsigned
+/// integers, so 32-bit versions would be more useful for protocol operations.
+/// However, with 32-bit integers we need to take into account wrap-around
+/// points and compare values using the serial number arithmetic as specified
+/// in RFC4034, which would be more error prone. We therefore provide 64-bit
+/// versions, too.
+///
+/// The timezone is always UTC for these functions.
+//@{
+/// Convert textual DNSSEC time to integer, 64-bit version.
+///
+/// The textual form must only consist of digits and be in the form of
+/// YYYYMMDDHHmmSS, where:
+/// - YYYY must be between 1970 and 9999
+/// - MM must be between 01 and 12
+/// - DD must be between 01 and 31 and must be a valid day for the month
+/// represented in 'MM'. For example, if MM is 04, DD cannot be 31.
+/// DD can be 29 when MM is 02 only when YYYY is a leap year.
+/// - HH must be between 00 and 23
+/// - mm must be between 00 and 59
+/// - SS must be between 00 and 60
+///
+/// For all fields the range includes the begin and end values. Note that
+/// 60 is allowed for 'SS', intending a leap second, although in real operation
+/// it's unlikely to be specified.
+///
+/// If the given text is valid, this function converts it to an unsigned
+/// 64-bit number of seconds since epoch (1 January 1970 00:00:00) and returns
+/// the converted value. 64 bits are sufficient to represent all possible
+/// values for the valid format uniquely, so there is no overflow.
+///
+/// \note RFC4034 also defines the textual form of an unsigned decimal integer
+/// for the corresponding time in seconds. This function doesn't support
+/// this form, and if given it throws an exception of class \c InvalidTime.
+///
+/// \exception InvalidTime The given textual representation is invalid.
+///
+/// \param time_txt Textual time in the form of YYYYMMDDHHmmSS
+/// \return Seconds since epoch corresponding to \c time_txt
+uint64_t
+timeFromText64(const std::string& time_txt);
+
+/// Convert textual DNSSEC time to integer, 32-bit version.
+///
+/// This version is the same as \c timeFromText64() except that the return
+/// value is wrapped around to an unsigned 32-bit integer, simply dropping
+/// the upper 32 bits.
+uint32_t
+timeFromText32(const std::string& time_txt);
+
+/// Convert integral DNSSEC time to textual form, 64-bit version.
+///
+/// This function takes an integer that would be seconds since epoch and
+/// converts it in the form of YYYYMMDDHHmmSS. For example, if \c value is
+/// 0, it returns "19700101000000". If the value corresponds to a point
+/// of time on and after year 10,000, which cannot be represented in the
+/// YYYY... form, an exception of class \c InvalidTime will be thrown.
+///
+/// \exception InvalidTime The given time specifies on or after year 10,000.
+/// \exception Other A standard exception, if resource allocation for the
+/// returned text fails.
+///
+/// \param value Seconds since epoch to be converted.
+/// \return Textual representation of \c value in the form of YYYYMMDDHHmmSS.
+std::string
+timeToText64(uint64_t value);
+
+/// Convert integral DNSSEC time to textual form, 32-bit version.
+///
+/// This version is the same as \c timeToText64(), but the time value
+/// is expected to be the lower 32 bits of the full 64-bit value.
+/// These two will be different on and after a certain point of time
+/// in year 2106, so this function internally resolves the ambiguity
+/// using the current system time at the time of function call;
+/// it first identifies the range of [N*2^32 - 2^31, N*2^32 + 2^31)
+/// that contains the current time, and interprets \c value in the context
+/// of that range. It then applies the same process as \c timeToText64().
+///
+/// There is one important exception in this processing, however.
+/// Until 19 Jan 2038 03:14:08 (2^31 seconds since epoch), this range
+/// would contain time before epoch. In order to ensure the returned
+/// value is also a valid input to \c timeFromText, this function uses
+/// a special range [0, 2^32) until that time. As a result, all upper
+/// half of the 32-bit values are treated as a future time. For example,
+/// 2^32-1 (the highest value in 32-bit unsigned integers) will be converted
+/// to "21060207062815", instead of "19691231235959".
+std::string
+timeToText32(const uint32_t value);
+
+//@}
+}
+}
+
+#endif // TIME_UTILITIES_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/triplet.h b/src/lib/util/triplet.h
new file mode 100644
index 0000000..fb2a645
--- /dev/null
+++ b/src/lib/util/triplet.h
@@ -0,0 +1,127 @@
+// Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TRIPLET_H
+#define TRIPLET_H
+
+#include <exceptions/exceptions.h>
+#include <util/optional.h>
+
+namespace isc {
+namespace util {
+
+/// @brief This template specifies a parameter value
+///
+/// This template class is used to store configuration parameters, like lifetime
+/// or T1. It defines 3 parameters: min, default, and max value. If the
+/// particular configuration parameter is not mandatory, it is possible to
+/// mark the parameter described by a @c Triplet "unspecified". For example, the
+/// T1 and T2 values in DHCPv4 server are optional and may be not specified
+/// in the configuration. The @c Triplets describing these parameters will be
+/// marked "unspecified". If the server finds that the particular parameter
+/// is unspecified it will not include it (e.g. option 58 or 59) in the message
+/// to a client.
+///
+/// There are 3 constructors:
+/// - without parameters - marks the parameter "unspecified"
+/// - simple (just one value that sets all parameters)
+/// - extended (that sets default value and two thresholds)
+///
+/// It will be used with integer types. It provides necessary operators, so
+/// it can be assigned to a plain integer or integer assigned to a Triplet.
+/// See TripletTest.operator test for details on an easy Triplet usage.
+template <class T>
+class Triplet : public util::Optional<T> {
+public:
+
+ using util::Optional<T>::get;
+
+ /// @brief Base type to Triplet conversion.
+ ///
+ /// Typically: uint32_t to Triplet assignment. It is very convenient
+ /// to be able to simply write Triplet<uint32_t> x = 7;
+ ///
+ /// @param other A number to be assigned as min, max and default value.
+ Triplet<T>& operator=(T other) {
+ min_ = other;
+ util::Optional<T>::default_ = other;
+ max_ = other;
+ // The value is now specified because we just assigned one.
+ util::Optional<T>::unspecified_ = false;
+ return (*this);
+ }
+
+ /// @brief Constructor without parameters.
+ ///
+ /// Marks value in @c Triplet unspecified.
+ Triplet()
+ : util::Optional<T>(), min_(0), max_(0) {
+ }
+
+ /// @brief Sets a fixed value.
+ ///
+ /// This constructor assigns a fixed (i.e. no range, just a single value)
+ /// value.
+ ///
+ /// @param value A number to be assigned as min, max and default value.
+ Triplet(T value)
+ : util::Optional<T>(value), min_(value), max_(value) {
+ }
+
+ /// @brief Sets the default value and thresholds
+ ///
+ /// @throw BadValue if min <= def <= max rule is violated
+ Triplet(T min, T def, T max)
+ : util::Optional<T>(def), min_(min), max_(max) {
+ if ( (min_ > def) || (def > max_) ) {
+ isc_throw(BadValue, "Invalid triplet values.");
+ }
+ }
+
+ /// @brief Returns a minimum allowed value
+ T getMin() const { return (min_);}
+
+ /// @brief Returns value with a hint
+ ///
+ /// DHCP protocol treats any values sent by a client as hints.
+ /// This is a method that implements that. We can assign any value
+ /// from configured range that client asks.
+ ///
+ /// @param hint A value being returned when if it is within the range
+ /// between min and max value of @c Triplet. If the hint value is lower
+ /// than min value, the min value is returned. if the hint is greater
+ /// than max value, the max value is returned.
+ ///
+ /// @return A value adjusted to the hint.
+ T get(T hint) const {
+ if (hint <= min_) {
+ return (min_);
+ }
+
+ if (hint >= max_) {
+ return (max_);
+ }
+
+ return (hint);
+ }
+
+ /// @brief Returns a maximum allowed value
+ T getMax() const { return (max_); }
+
+private:
+
+ /// @brief the minimum value
+ T min_;
+
+ /// @brief the maximum value
+ T max_;
+};
+
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // TRIPLET_H
diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am
new file mode 100644
index 0000000..2d6523c
--- /dev/null
+++ b/src/lib/util/unittests/Makefile.am
@@ -0,0 +1,32 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+noinst_LTLIBRARIES = libutil_unittests.la
+libutil_unittests_la_SOURCES = fork.h fork.cc
+libutil_unittests_la_SOURCES += newhook.h newhook.cc
+libutil_unittests_la_SOURCES += testdata.h testdata.cc
+if HAVE_GTEST
+libutil_unittests_la_SOURCES += resource.h resource.cc
+libutil_unittests_la_SOURCES += check_valgrind.h check_valgrind.cc
+libutil_unittests_la_SOURCES += run_all.h run_all.cc
+libutil_unittests_la_SOURCES += textdata.h
+libutil_unittests_la_SOURCES += wiredata.h wiredata.cc
+libutil_unittests_la_SOURCES += interprocess_util.h interprocess_util.cc
+endif
+
+# For now, this isn't needed for libutil_unittests
+EXTRA_DIST = mock_socketsession.h
+
+libutil_unittests_la_CPPFLAGS = $(AM_CPPFLAGS)
+if HAVE_GTEST
+libutil_unittests_la_CPPFLAGS += $(GTEST_INCLUDES)
+endif
+
+libutil_unittests_la_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+libutil_unittests_la_LIBADD = $(top_builddir)/src/lib/util/libkea-util.la
+libutil_unittests_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+if HAVE_GTEST
+libutil_unittests_la_LIBADD += $(GTEST_LDADD)
+endif
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/unittests/Makefile.in b/src/lib/util/unittests/Makefile.in
new file mode 100644
index 0000000..46ecf8f
--- /dev/null
+++ b/src/lib/util/unittests/Makefile.in
@@ -0,0 +1,846 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@HAVE_GTEST_TRUE@am__append_1 = resource.h resource.cc \
+@HAVE_GTEST_TRUE@ check_valgrind.h check_valgrind.cc run_all.h \
+@HAVE_GTEST_TRUE@ run_all.cc textdata.h wiredata.h wiredata.cc \
+@HAVE_GTEST_TRUE@ interprocess_util.h interprocess_util.cc
+@HAVE_GTEST_TRUE@am__append_2 = $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@am__append_3 = $(GTEST_LDADD)
+subdir = src/lib/util/unittests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1)
+libutil_unittests_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_2)
+am__libutil_unittests_la_SOURCES_DIST = fork.h fork.cc newhook.h \
+ newhook.cc testdata.h testdata.cc resource.h resource.cc \
+ check_valgrind.h check_valgrind.cc run_all.h run_all.cc \
+ textdata.h wiredata.h wiredata.cc interprocess_util.h \
+ interprocess_util.cc
+@HAVE_GTEST_TRUE@am__objects_1 = libutil_unittests_la-resource.lo \
+@HAVE_GTEST_TRUE@ libutil_unittests_la-check_valgrind.lo \
+@HAVE_GTEST_TRUE@ libutil_unittests_la-run_all.lo \
+@HAVE_GTEST_TRUE@ libutil_unittests_la-wiredata.lo \
+@HAVE_GTEST_TRUE@ libutil_unittests_la-interprocess_util.lo
+am_libutil_unittests_la_OBJECTS = libutil_unittests_la-fork.lo \
+ libutil_unittests_la-newhook.lo \
+ libutil_unittests_la-testdata.lo $(am__objects_1)
+libutil_unittests_la_OBJECTS = $(am_libutil_unittests_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libutil_unittests_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libutil_unittests_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = \
+ ./$(DEPDIR)/libutil_unittests_la-check_valgrind.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-fork.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-interprocess_util.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-newhook.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-resource.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-run_all.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-testdata.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-wiredata.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libutil_unittests_la_SOURCES)
+DIST_SOURCES = $(am__libutil_unittests_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp README
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+noinst_LTLIBRARIES = libutil_unittests.la
+libutil_unittests_la_SOURCES = fork.h fork.cc newhook.h newhook.cc \
+ testdata.h testdata.cc $(am__append_1)
+
+# For now, this isn't needed for libutil_unittests
+EXTRA_DIST = mock_socketsession.h
+libutil_unittests_la_CPPFLAGS = $(AM_CPPFLAGS) $(am__append_2)
+libutil_unittests_la_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+libutil_unittests_la_LIBADD = \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__append_3)
+CLEANFILES = *.gcno *.gcda
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/util/unittests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/unittests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libutil_unittests.la: $(libutil_unittests_la_OBJECTS) $(libutil_unittests_la_DEPENDENCIES) $(EXTRA_libutil_unittests_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libutil_unittests_la_LINK) $(libutil_unittests_la_OBJECTS) $(libutil_unittests_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-check_valgrind.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-fork.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-interprocess_util.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-newhook.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-resource.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-run_all.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-testdata.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-wiredata.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libutil_unittests_la-fork.lo: fork.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-fork.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-fork.Tpo -c -o libutil_unittests_la-fork.lo `test -f 'fork.cc' || echo '$(srcdir)/'`fork.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-fork.Tpo $(DEPDIR)/libutil_unittests_la-fork.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fork.cc' object='libutil_unittests_la-fork.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-fork.lo `test -f 'fork.cc' || echo '$(srcdir)/'`fork.cc
+
+libutil_unittests_la-newhook.lo: newhook.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-newhook.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-newhook.Tpo -c -o libutil_unittests_la-newhook.lo `test -f 'newhook.cc' || echo '$(srcdir)/'`newhook.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-newhook.Tpo $(DEPDIR)/libutil_unittests_la-newhook.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='newhook.cc' object='libutil_unittests_la-newhook.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-newhook.lo `test -f 'newhook.cc' || echo '$(srcdir)/'`newhook.cc
+
+libutil_unittests_la-testdata.lo: testdata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-testdata.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-testdata.Tpo -c -o libutil_unittests_la-testdata.lo `test -f 'testdata.cc' || echo '$(srcdir)/'`testdata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-testdata.Tpo $(DEPDIR)/libutil_unittests_la-testdata.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='testdata.cc' object='libutil_unittests_la-testdata.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-testdata.lo `test -f 'testdata.cc' || echo '$(srcdir)/'`testdata.cc
+
+libutil_unittests_la-resource.lo: resource.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-resource.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-resource.Tpo -c -o libutil_unittests_la-resource.lo `test -f 'resource.cc' || echo '$(srcdir)/'`resource.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-resource.Tpo $(DEPDIR)/libutil_unittests_la-resource.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='resource.cc' object='libutil_unittests_la-resource.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-resource.lo `test -f 'resource.cc' || echo '$(srcdir)/'`resource.cc
+
+libutil_unittests_la-check_valgrind.lo: check_valgrind.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-check_valgrind.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-check_valgrind.Tpo -c -o libutil_unittests_la-check_valgrind.lo `test -f 'check_valgrind.cc' || echo '$(srcdir)/'`check_valgrind.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-check_valgrind.Tpo $(DEPDIR)/libutil_unittests_la-check_valgrind.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='check_valgrind.cc' object='libutil_unittests_la-check_valgrind.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-check_valgrind.lo `test -f 'check_valgrind.cc' || echo '$(srcdir)/'`check_valgrind.cc
+
+libutil_unittests_la-run_all.lo: run_all.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-run_all.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-run_all.Tpo -c -o libutil_unittests_la-run_all.lo `test -f 'run_all.cc' || echo '$(srcdir)/'`run_all.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-run_all.Tpo $(DEPDIR)/libutil_unittests_la-run_all.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_all.cc' object='libutil_unittests_la-run_all.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-run_all.lo `test -f 'run_all.cc' || echo '$(srcdir)/'`run_all.cc
+
+libutil_unittests_la-wiredata.lo: wiredata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-wiredata.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-wiredata.Tpo -c -o libutil_unittests_la-wiredata.lo `test -f 'wiredata.cc' || echo '$(srcdir)/'`wiredata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-wiredata.Tpo $(DEPDIR)/libutil_unittests_la-wiredata.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='wiredata.cc' object='libutil_unittests_la-wiredata.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-wiredata.lo `test -f 'wiredata.cc' || echo '$(srcdir)/'`wiredata.cc
+
+libutil_unittests_la-interprocess_util.lo: interprocess_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-interprocess_util.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-interprocess_util.Tpo -c -o libutil_unittests_la-interprocess_util.lo `test -f 'interprocess_util.cc' || echo '$(srcdir)/'`interprocess_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-interprocess_util.Tpo $(DEPDIR)/libutil_unittests_la-interprocess_util.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='interprocess_util.cc' object='libutil_unittests_la-interprocess_util.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-interprocess_util.lo `test -f 'interprocess_util.cc' || echo '$(srcdir)/'`interprocess_util.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-check_valgrind.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-fork.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-interprocess_util.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-newhook.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-resource.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-run_all.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-testdata.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-wiredata.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-check_valgrind.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-fork.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-interprocess_util.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-newhook.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-resource.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-run_all.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-testdata.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-wiredata.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/util/unittests/README b/src/lib/util/unittests/README
new file mode 100644
index 0000000..0ed888f
--- /dev/null
+++ b/src/lib/util/unittests/README
@@ -0,0 +1,5 @@
+This directory contains some code that is useful while writing various
+unittest code. It doesn't contain any code that would actually run in
+bind10.
+
+Because this is a test code, we do not test it explicitly.
diff --git a/src/lib/util/unittests/check_valgrind.cc b/src/lib/util/unittests/check_valgrind.cc
new file mode 100644
index 0000000..8412d43
--- /dev/null
+++ b/src/lib/util/unittests/check_valgrind.cc
@@ -0,0 +1,33 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+#if HAVE_VALGRIND_HEADERS
+#include <valgrind/valgrind.h>
+/// \brief Check if the program is run in valgrind
+///
+/// \return true if valgrind headers are available, and valgrind is running,
+/// false if the headers are not available, or if valgrind is not
+/// running
+bool
+runningOnValgrind() {
+ return (RUNNING_ON_VALGRIND != 0);
+}
+#else
+bool
+runningOnValgrind() {
+ return (false);
+}
+#endif // HAVE_VALGRIND_HEADERS
+
+} // end of namespace unittests
+} // end of namespace util
+} // end of namespace isc
diff --git a/src/lib/util/unittests/check_valgrind.h b/src/lib/util/unittests/check_valgrind.h
new file mode 100644
index 0000000..0abe39f
--- /dev/null
+++ b/src/lib/util/unittests/check_valgrind.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+//
+// If we have the valgrind headers available, we can detect whether
+// valgrind is running. This should normally never be done, as you
+// want the to test the actual code in operation with valgrind.
+//
+// However, there is a limited set of operations where we want to
+// skip some tests if run under valgrind, most notably the
+// EXPECT_DEATH tests, as these would report memory leaks by
+// definition.
+//
+// If the valgrind headers are NOT available, the method checkValgrind()
+// always returns false; i.e. it always pretends the program is run
+// natively
+//
+
+#ifndef UTIL_UNITTESTS_CHECK_VALGRIND_H
+#define UTIL_UNITTESTS_CHECK_VALGRIND_H 1
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Check if the program is run in valgrind
+///
+/// This is used to check for valgrind and skip (parts of) tests that fork,
+/// such as death tests, and general forking tests, and some threading tests;
+/// These tend to cause valgrind to report errors, which would hide other
+/// potential valgrind reports.
+///
+/// \return true if valgrind headers are available, and valgrind is running,
+/// false if the headers are not available, or if valgrind is not
+/// running
+bool runningOnValgrind();
+
+} // end namespace unittests
+} // end namespace util
+} // end namespace isc
+
+#endif // UTIL_UNITTESTS_CHECK_VALGRIND_H
diff --git a/src/lib/util/unittests/fork.cc b/src/lib/util/unittests/fork.cc
new file mode 100644
index 0000000..fc8b525
--- /dev/null
+++ b/src/lib/util/unittests/fork.cc
@@ -0,0 +1,141 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/unittests/fork.h>
+
+#include <util/io/fd.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <cerrno>
+#include <stdlib.h>
+#include <stdio.h>
+
+using namespace isc::util::io;
+
+namespace {
+
+// Just a NOP function to ignore a signal but let it interrupt function.
+void no_handler(int) { }
+
+};
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+bool
+process_ok(pid_t process) {
+ // Create a timeout
+ struct sigaction ignored, original;
+ memset(&ignored, 0, sizeof ignored);
+ ignored.sa_handler = no_handler;
+ if (sigaction(SIGALRM, &ignored, &original)) {
+ return false;
+ }
+ // It is long, but if everything is OK, it'll not happen
+ alarm(10);
+ int status;
+ int result(waitpid(process, &status, 0) == -1);
+ // Cancel the alarm and return the original handler
+ alarm(0);
+ if (sigaction(SIGALRM, &original, NULL)) {
+ return false;
+ }
+ // Check what we found out
+ if (result) {
+ if (errno == EINTR)
+ kill(process, SIGTERM);
+ return false;
+ }
+ return WIFEXITED(status) && WEXITSTATUS(status) == 0;
+}
+
+/*
+ * This creates a pipe, forks and feeds the pipe with given data.
+ * Used to provide the input in non-blocking/asynchronous way.
+ */
+pid_t
+provide_input(int *read_pipe, const void *input, const size_t length)
+{
+ int pipes[2];
+ if (pipe(pipes)) {
+ return -1;
+ }
+ *read_pipe = pipes[0];
+
+ pid_t pid(fork());
+ if (pid) { // We are in the parent
+ return pid;
+ } else { // This is in the child, just puts the data there
+ close(pipes[0]);
+ if (!write_data(pipes[1], input, length)) {
+ exit(1);
+ } else {
+ close(pipes[1]);
+ exit(0);
+ }
+ }
+}
+
+
+/*
+ * This creates a pipe, forks and reads the pipe and compares it
+ * with given data. Used to check output of run in an asynchronous way.
+ */
+pid_t
+check_output(int *write_pipe, const void* const output, const size_t length)
+{
+ int pipes[2];
+ if (pipe(pipes)) {
+ return -1;
+ }
+ *write_pipe = pipes[1];
+ pid_t pid(fork());
+ if (pid) { // We are in parent
+ close(pipes[0]);
+ return pid;
+ } else {
+ close(pipes[1]);
+ unsigned char* buffer = new unsigned char[length + 1];
+ // Try to read one byte more to see if the output ends here
+ size_t got_length(read_data(pipes[0], buffer, length + 1));
+ bool ok(true);
+ if (got_length != length) {
+ fprintf(stderr, "Different length (expected %u, got %u)\n",
+ static_cast<unsigned>(length),
+ static_cast<unsigned>(got_length));
+ ok = false;
+ }
+ if(!ok || memcmp(buffer, output, length)) {
+ const unsigned char *output_c(static_cast<const unsigned char *>(
+ output));
+ // If they differ, print what we have
+ for(size_t i(0); i != got_length; ++ i) {
+ fprintf(stderr, "%02hhx", buffer[i]);
+ }
+ fprintf(stderr, "\n");
+ for(size_t i(0); i != length; ++ i) {
+ fprintf(stderr, "%02hhx", output_c[i]);
+ }
+ fprintf(stderr, "\n");
+ delete [] buffer;
+ exit(1);
+ } else {
+ delete [] buffer;
+ exit(0);
+ }
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/util/unittests/fork.h b/src/lib/util/unittests/fork.h
new file mode 100644
index 0000000..68277f6
--- /dev/null
+++ b/src/lib/util/unittests/fork.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UTIL_UNITTESTS_FORK_H
+#define UTIL_UNITTESTS_FORK_H 1
+
+#include <unistd.h>
+
+/**
+ * \file fork.h
+ * \brief Help functions to fork the test case process.
+ * Various functions to fork a process and feed some data to pipe, check
+ * its output and such lives here.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/**
+ * @short Checks that a process terminates correctly.
+ * Waits for a process to terminate (with a short timeout, this should be
+ * used whan the process is about to terminate) and checks its exit code.
+ *
+ * @return True if the process terminates with 0, false otherwise.
+ * @param process The ID of process to wait for.
+ */
+bool
+process_ok(pid_t process);
+
+pid_t
+provide_input(int* read_pipe, const void* input, const size_t length);
+
+pid_t
+check_output(int* write_pipe, const void* const output, const size_t length);
+
+} // End of the namespace
+}
+}
+
+#endif // UTIL_UNITTESTS_FORK_H
diff --git a/src/lib/util/unittests/interprocess_util.cc b/src/lib/util/unittests/interprocess_util.cc
new file mode 100644
index 0000000..dd639ff
--- /dev/null
+++ b/src/lib/util/unittests/interprocess_util.cc
@@ -0,0 +1,42 @@
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/select.h>
+#include <cstddef>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+unsigned char
+parentReadState(int fd) {
+ unsigned char result = 0xff;
+
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+
+ struct timeval tv = {5, 0};
+
+ const int nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
+ EXPECT_EQ(1, nfds);
+
+ if (nfds == 1) {
+ // Read status
+ const ssize_t bytes_read = read(fd, &result, sizeof(result));
+ EXPECT_EQ(sizeof(result), bytes_read);
+ }
+
+ return (result);
+}
+
+}
+}
+}
diff --git a/src/lib/util/unittests/interprocess_util.h b/src/lib/util/unittests/interprocess_util.h
new file mode 100644
index 0000000..7012f7b
--- /dev/null
+++ b/src/lib/util/unittests/interprocess_util.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// \brief A helper utility for a simple synchronization with another process.
+///
+/// It waits for incoming data on a given file descriptor up to 5 seconds
+/// (arbitrary choice), read one byte data, and return it to the caller.
+/// On any failure it returns 0xff (255), so the sender process should use
+/// a different value to pass.
+unsigned char parentReadState(int fd);
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/mock_socketsession.h b/src/lib/util/unittests/mock_socketsession.h
new file mode 100644
index 0000000..808cddb
--- /dev/null
+++ b/src/lib/util/unittests/mock_socketsession.h
@@ -0,0 +1,151 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UTIL_UNITTESTS_MOCKSOCKETSESSION_H
+#define UTIL_UNITTESTS_MOCKSOCKETSESSION_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <util/io/socketsession.h>
+#include <util/io/sockaddr_util.h>
+
+#include <cassert>
+#include <cstring>
+#include <vector>
+
+#include <sys/socket.h>
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Mock socket session forwarder.
+///
+/// It emulates the behavior of SocketSessionForwarder without involving
+/// network communication, and allowing the tester to customize the behavior
+/// and to examine forwarded data afterwards.
+class MockSocketSessionForwarder :
+ public isc::util::io::BaseSocketSessionForwarder
+{
+public:
+ MockSocketSessionForwarder() :
+ is_connected_(false), connect_ok_(true), push_ok_(true),
+ close_ok_(true),
+ // These are not used until set, but we set them anyway here,
+ // partly to silence cppcheck, and partly to be cleaner. Put some
+ // invalid values in.
+ pushed_sock_(-1), pushed_family_(-1), pushed_type_(-1),
+ pushed_protocol_(-1)
+ {}
+
+ virtual void connectToReceiver() {
+ if (!connect_ok_) {
+ isc_throw(isc::util::io::SocketSessionError, "socket session "
+ "forwarding connection disabled for test");
+ }
+ if (is_connected_) {
+ isc_throw(isc::util::io::SocketSessionError, "duplicate connect");
+ }
+ is_connected_ = true;
+ }
+ virtual void close() {
+ if (!is_connected_) {
+ isc_throw(isc::util::io::SocketSessionError, "duplicate close");
+ }
+ is_connected_ = false;
+ }
+
+ // Pushing a socket session. It copies the given session data
+ // so that the test code can check the values later via the getter
+ // methods. Complete deep copy will be created, so the caller doesn't
+ // have to keep the parameters valid after the call to this method.
+ virtual void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len)
+ {
+ if (!push_ok_) {
+ isc_throw(isc::util::io::SocketSessionError,
+ "socket session forwarding is disabled for test");
+ }
+ if (!is_connected_) {
+ isc_throw(isc::util::io::SocketSessionError,
+ "socket session is being pushed before connected");
+ }
+
+ // Copy parameters for later checks
+ pushed_sock_ = sock;
+ pushed_family_ = family;
+ pushed_type_ = type;
+ pushed_protocol_ = protocol;
+ assert(io::internal::getSALength(local_end) <=
+ sizeof(pushed_local_end_ss_));
+ std::memcpy(&pushed_local_end_ss_, &local_end,
+ io::internal::getSALength(local_end));
+ assert(io::internal::getSALength(remote_end) <=
+ sizeof(pushed_remote_end_ss_));
+ std::memcpy(&pushed_remote_end_ss_, &remote_end,
+ io::internal::getSALength(remote_end));
+ pushed_data_.resize(data_len);
+ std::memcpy(&pushed_data_[0], data, data_len);
+ }
+
+ // Allow the test code to check if the connection is established.
+ bool isConnected() const { return (is_connected_); }
+
+ // Allow the test code to customize the forwarder behavior wrt whether
+ // a specific operation should succeed or fail.
+ void disableConnect() { connect_ok_ = false; }
+ void enableConnect() { connect_ok_ = true; }
+ void disableClose() { close_ok_ = false; }
+ void disablePush() { push_ok_ = false; }
+ void enablePush() { push_ok_ = true; }
+
+ // Read-only accessors to recorded parameters to the previous successful
+ // call to push(). Return values are undefined if there has been no
+ // successful call to push().
+ // Note that we use convertSockAddr() to convert sockaddr_storage to
+ // sockaddr. It should be safe since we use the storage in its literal
+ // sense; it was originally filled with the binary image of another
+ // sockaddr structure, and we are going to return the image opaquely
+ // as a sockaddr structure without touching the data.
+ int getPushedSock() const { return (pushed_sock_); }
+ int getPushedFamily() const { return (pushed_family_); }
+ int getPushedType() const { return (pushed_type_); }
+ int getPushedProtocol() const { return (pushed_protocol_); }
+ const struct sockaddr& getPushedLocalend() const {
+ return (*io::internal::convertSockAddr(&pushed_local_end_ss_));
+ }
+ const struct sockaddr& getPushedRemoteend() const {
+ return (*io::internal::convertSockAddr(&pushed_remote_end_ss_));
+ }
+ const std::vector<uint8_t>& getPushedData() const {
+ return (pushed_data_);
+ }
+
+private:
+ bool is_connected_;
+ bool connect_ok_;
+ bool push_ok_;
+ bool close_ok_;
+ int pushed_sock_;
+ int pushed_family_;
+ int pushed_type_;
+ int pushed_protocol_;
+ struct sockaddr_storage pushed_local_end_ss_;
+ struct sockaddr_storage pushed_remote_end_ss_;
+ std::vector<uint8_t> pushed_data_;
+};
+
+} // end of unittests
+} // end of util
+} // end of isc
+#endif // UTIL_UNITTESTS_MOCKSOCKETSESSION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/newhook.cc b/src/lib/util/unittests/newhook.cc
new file mode 100644
index 0000000..16c601a
--- /dev/null
+++ b/src/lib/util/unittests/newhook.cc
@@ -0,0 +1,45 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <new>
+#include <stdexcept>
+
+#include <util/unittests/newhook.h>
+
+#ifdef ENABLE_CUSTOM_OPERATOR_NEW
+void*
+operator new(size_t size) throw(std::bad_alloc) {
+ if (isc::util::unittests::force_throw_on_new &&
+ size == isc::util::unittests::throw_size_on_new) {
+ throw std::bad_alloc();
+ }
+ void* p = malloc(size);
+ if (p == NULL) {
+ throw std::bad_alloc();
+ }
+ return (p);
+}
+
+void
+operator delete(void* p) throw() {
+ if (p != NULL) {
+ free(p);
+ }
+}
+#endif
+
+namespace isc {
+namespace util {
+namespace unittests {
+bool force_throw_on_new = false;
+size_t throw_size_on_new = 0;
+}
+}
+}
diff --git a/src/lib/util/unittests/newhook.h b/src/lib/util/unittests/newhook.h
new file mode 100644
index 0000000..1fc3eba
--- /dev/null
+++ b/src/lib/util/unittests/newhook.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UTIL_UNITTESTS_NEWHOOK_H
+#define UTIL_UNITTESTS_NEWHOOK_H 1
+
+/**
+ * \file newhook.h
+ * \brief Enable the use of special operator new that throws for testing.
+ *
+ * This small utility allows a test case to force the global operator new
+ * to throw for a given size to test a case where memory allocation fails
+ * (which normally doesn't happen). To enable the feature, everything must
+ * be built with defining ENABLE_CUSTOM_OPERATOR_NEW beforehand, and set
+ * \c force_throw_on_new to \c true and \c throw_size_on_new to the size
+ * of data that should trigger the exception, immediately before starting
+ * the specific test that needs the exception.
+ *
+ * Example:
+ * \code #include <util/unittests/newhook.h>
+ * ...
+ * TEST(SomeTest, newException) {
+ * isc::util::unittests::force_throw_on_new = true;
+ * isc::util::unittests::throw_size_on_new = sizeof(Foo);
+ * try {
+ * // this will do 'new Foo()' internally and should throw
+ * createFoo();
+ * isc::util::unittests::force_throw_on_new = false;
+ * ASSERT_FALSE(true) << "Expected throw on new";
+ * } catch (const std::bad_alloc&) {
+ * isc::util::unittests::force_throw_on_new = false;
+ * // do some integrity check, etc, if necessary
+ * }
+ * } \endcode
+ *
+ * Replacing the global operator new (and delete) is a dangerous technique,
+ * and triggering an exception solely based on the allocation size is not
+ * reliable, so this feature is disabled by default two-fold: The
+ * ENABLE_CUSTOM_OPERATOR_NEW build time variable, and run-time
+ * \c force_throw_on_new.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// Switch to enable the use of special operator new
+///
+/// This is set to \c false by default.
+extern bool force_throw_on_new;
+
+/// The allocation size that triggers an exception in the special operator new
+///
+/// This is the exact size that causes an exception to be thrown;
+/// for example, if it is set to 100, an attempt of allocating 100 bytes
+/// will result in an exception, but allocation attempt for 101 bytes won't
+/// (unless, of course, memory is really exhausted and allocation really
+/// fails).
+///
+/// The default value is 0. The value of this variable has no meaning
+/// unless the use of the special operator is enabled at build time and
+/// via \c force_throw_on_new.
+extern size_t throw_size_on_new;
+}
+}
+}
+
+#endif // UTIL_UNITTESTS_NEWHOOK_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/resource.cc b/src/lib/util/unittests/resource.cc
new file mode 100644
index 0000000..b4a33b3
--- /dev/null
+++ b/src/lib/util/unittests/resource.cc
@@ -0,0 +1,29 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/unittests/resource.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+dontCreateCoreDumps() {
+ const rlimit core_limit = {0, 0};
+
+ EXPECT_EQ(setrlimit(RLIMIT_CORE, &core_limit), 0);
+}
+
+} // end of namespace unittests
+} // end of namespace util
+} // end of namespace isc
diff --git a/src/lib/util/unittests/resource.h b/src/lib/util/unittests/resource.h
new file mode 100644
index 0000000..dfa44ee
--- /dev/null
+++ b/src/lib/util/unittests/resource.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UTIL_UNITTESTS_RESOURCE_H
+#define UTIL_UNITTESTS_RESOURCE_H 1
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// Don't create core dumps.
+///
+/// This function sets the core size to 0, inhibiting the creation of
+/// core dumps. It is meant to be used in testcases where EXPECT_DEATH
+/// is used, where processes abort (and create cores in the process).
+/// As a new process is forked to run EXPECT_DEATH tests, the rlimits of
+/// the parent process that runs the other tests should be unaffected.
+void dontCreateCoreDumps();
+
+} // end of namespace unittests
+} // end of namespace util
+} // end of namespace isc
+
+#endif // UTIL_UNITTESTS_RESOURCE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/run_all.cc b/src/lib/util/unittests/run_all.cc
new file mode 100644
index 0000000..82542ca
--- /dev/null
+++ b/src/lib/util/unittests/run_all.cc
@@ -0,0 +1,89 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include <iostream>
+#include <iomanip>
+
+#include <gtest/gtest.h>
+#include <exceptions/exceptions.h>
+#include <util/unittests/run_all.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+int
+run_all() {
+ int ret = 0;
+
+ // The catching of exceptions generated in tests is controlled by the
+ // KEATEST_CATCH_EXCEPTION environment variable. Setting this to
+ // 1 enables the catching of exceptions; setting it to 0 disables it.
+ // Anything else causes a message to be printed to stderr and the default
+ // taken. (The default is to catch exceptions if compiling with clang
+ // and false if not.)
+#ifdef __clang__
+ bool catch_exception = true;
+#else
+ bool catch_exception = false;
+#endif
+
+ const char* keatest_catch_exception = getenv("KEATEST_CATCH_EXCEPTION");
+ if (keatest_catch_exception != NULL) {
+ if (strcmp(keatest_catch_exception, "1") == 0) {
+ catch_exception = true;
+ } else if (strcmp(keatest_catch_exception, "0") == 0) {
+ catch_exception = false;
+ } else {
+ std::cerr << "***ERROR: KEATEST_CATCH_EXCEPTION is '"
+ << keatest_catch_exception
+ << "': allowed values are '1' or '0'.\n"
+ << " The default value of "
+ << (catch_exception ?
+ "1 (exception catching enabled)":
+ "0 (exception catching disabled)")
+ << " will be used.\n";
+ }
+ }
+
+ // Actually run the code
+ if (catch_exception) {
+ try {
+ ret = RUN_ALL_TESTS();
+ } catch (const isc::Exception& ex) {
+ // Could output more information with typeid(), but there is no
+ // guarantee that all compilers will support it without an explicit
+ // flag on the command line.
+ std::cerr << "*** Exception derived from isc::exception thrown:\n"
+ << " file: " << ex.getFile() << "\n"
+ << " line: " << ex.getLine() << "\n"
+ << " what: " << ex.what() << std::endl;
+ throw;
+ } catch (const std::exception& ex) {
+ std::cerr << "*** Exception derived from std::exception thrown:\n"
+ << " what: " << ex.what() << std::endl;
+ throw;
+ }
+ } else {
+ // This is a separate path for the case where the exception is not
+ // being caught. Although the other code path re-throws the exception
+ // after catching it, there is no guarantee that the state of the
+ // stack is preserved - a compiler might have unwound the stack to
+ // the point at which the exception is caught. This would prove
+ // awkward if trying to debug the program using a debugger.
+ ret = RUN_ALL_TESTS();
+ }
+
+ return (ret);
+}
+
+} // namespace unittests
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/unittests/run_all.h b/src/lib/util/unittests/run_all.h
new file mode 100644
index 0000000..7415e6b
--- /dev/null
+++ b/src/lib/util/unittests/run_all.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+
+#ifndef RUN_ALL_H
+#define RUN_ALL_H
+
+// Avoid need for user to include this header file.
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Run All Tests
+///
+/// A wrapper for the Google Test RUN_ALL_TESTS() macro, this calls the macro
+/// but wraps the call in a try...catch block if the environment variable
+/// KEATEST_CATCH_EXCEPTION is defined, and calls the macro directly if not.
+///
+/// The catch block catches exceptions of types isc::Exception and
+/// std::exception and prints some information about them to stderr. (In the
+/// case of isc::Exception, this includes the file and line number from which
+/// the exception was raised.) It then re-throws the exception.
+///
+/// See: https://lists.isc.org/pipermail/bind10-dev/2011-January/001867.html
+/// for some context.
+///
+/// \return Return value from RUN_ALL_TESTS().
+
+int run_all();
+
+} // namespace unittests
+} // namespace util
+} // namespace isc
+
+
+
+#endif // RUN_ALL_H
diff --git a/src/lib/util/unittests/testdata.cc b/src/lib/util/unittests/testdata.cc
new file mode 100644
index 0000000..e3ecc91
--- /dev/null
+++ b/src/lib/util/unittests/testdata.cc
@@ -0,0 +1,55 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+#include <stdexcept>
+#include <fstream>
+#include <vector>
+
+#include <util/unittests/testdata.h>
+
+using namespace std;
+
+namespace {
+vector<string>&
+getDataPaths() {
+ static vector<string> data_path;
+ return (data_path);
+}
+}
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+addTestDataPath(const string& path) {
+ getDataPaths().push_back(path);
+}
+
+void
+openTestData(const char* const datafile, ifstream& ifs) {
+ vector<string>::const_iterator it = getDataPaths().begin();
+ for (; it != getDataPaths().end(); ++it) {
+ string data_path = *it;
+ if (data_path.empty() || *data_path.rbegin() != '/') {
+ data_path.push_back('/');
+ }
+ ifs.open((data_path + datafile).c_str(), ios_base::in);
+ if (!ifs.fail()) {
+ return;
+ }
+ }
+
+ throw runtime_error("failed to open data file in data paths: " +
+ string(datafile));
+}
+
+}
+}
+}
diff --git a/src/lib/util/unittests/testdata.h b/src/lib/util/unittests/testdata.h
new file mode 100644
index 0000000..ee7c1c5
--- /dev/null
+++ b/src/lib/util/unittests/testdata.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UTIL_UNITTESTS_TESTDATA_H
+#define UTIL_UNITTESTS_TESTDATA_H 1
+
+/**
+ * \file testdata.h
+ * \brief Manipulating test data files.
+ *
+ * This utility defines functions that help test case handle test data
+ * stored in a file.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// Add a path (directory) that \c openTestData() will search for test data
+/// files.
+void addTestDataPath(const std::string& path);
+
+/// Open a file specified by 'datafile' using the data paths registered via
+/// addTestDataPath(). On success, ifs will be ready for reading the data
+/// stored in 'datafile'. If the data file cannot be open with any of the
+/// registered paths, a runtime_error exception will be thrown.
+///
+/// \note Care should be taken if you want to reuse the same single \c ifs
+/// for multiple input data. Some standard C++ library implementations retain
+/// the failure bit if the first stream reaches the end of the first file,
+/// and make the second call to \c ifstream::open() fail. The safest way
+/// is to use a different \c ifstream object for a new call to this function;
+/// alternatively make sure you explicitly clear the error bit by calling
+/// \c ifstream::clear() on \c ifs.
+void openTestData(const char* const datafile, std::ifstream& ifs);
+}
+}
+}
+
+#endif // UTIL_UNITTESTS_TESTDATA_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/textdata.h b/src/lib/util/unittests/textdata.h
new file mode 100644
index 0000000..ae132cd
--- /dev/null
+++ b/src/lib/util/unittests/textdata.h
@@ -0,0 +1,95 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <istream>
+#include <string>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+#ifndef UTIL_UNITTESTS_TEXTDATA_H
+#define UTIL_UNITTESTS_TEXTDATA_H 1
+
+/**
+ * \file textdata.h
+ * \brief Utilities for tests with text data.
+ *
+ * This utility provides convenient helper functions for unit tests using
+ * textual data.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// Line-by-line text comparison.
+///
+/// This templated function takes two standard input streams, extracts
+/// strings from them, and compares the two sets of strings line by line.
+template <typename EXPECTED_STREAM, typename ACTUAL_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, ACTUAL_STREAM& actual) {
+ std::string actual_line;
+ std::string expected_line;
+ while (std::getline(actual, actual_line), !actual.eof()) {
+ std::getline(expected, expected_line);
+ if (expected.eof()) {
+ FAIL() << "Redundant line in actual output: " << actual_line;
+ break;
+ }
+ if (actual.bad() || actual.fail() ||
+ expected.bad() || expected.fail()) {
+ throw std::runtime_error("Unexpected error in data streams");
+ }
+ EXPECT_EQ(expected_line, actual_line);
+ }
+ while (std::getline(expected, expected_line), !expected.eof()) {
+ ADD_FAILURE() << "Missing line in actual output: " << expected_line;
+ }
+}
+
+/// Similar to the fully templated version, but takes string for the second
+/// (actual) data.
+///
+/// Due to the nature of textual data, it will often be the case that test
+/// data is given as a string object. This shortcut version helps such cases
+/// so that the test code doesn't have to create a string stream with the
+/// string data just for testing.
+template <typename EXPECTED_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, const std::string& actual_text) {
+ std::istringstream iss(actual_text);
+ matchTextData(expected, iss);
+}
+
+/// Same for the previous version, but the first argument is string.
+template <typename ACTUAL_STREAM>
+void
+matchTextData(const std::string& expected_text, ACTUAL_STREAM& actual) {
+ std::istringstream iss(expected_text);
+ matchTextData(iss, actual);
+}
+
+/// Same for the previous two, but takes strings for both expected and
+/// actual data.
+void
+matchTextData(const std::string& expected_text,
+ const std::string& actual_text)
+{
+ std::istringstream expected_is(expected_text);
+ std::istringstream actual_is(actual_text);
+ matchTextData(expected_is, actual_is);
+}
+
+}
+}
+}
+
+#endif // UTIL_UNITTESTS_TEXTDATA_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/wiredata.cc b/src/lib/util/unittests/wiredata.cc
new file mode 100644
index 0000000..c14c8f1
--- /dev/null
+++ b/src/lib/util/unittests/wiredata.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/unittests/wiredata.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm> // for std::min
+#include <stdint.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+matchWireData(const void* expected_data, size_t expected_len,
+ const void* actual_data, size_t actual_len)
+{
+ const size_t cmplen = std::min(expected_len, actual_len);
+
+ for (size_t i = 0; i < cmplen; ++i) {
+ const int ebyte = static_cast<const uint8_t*>(expected_data)[i];
+ const int abyte = static_cast<const uint8_t*>(actual_data)[i];
+ // Once we find a mismatch, it's quite likely that there will be many
+ // mismatches after this point. So we stop here by using ASSERT not
+ // to be too noisy.
+ ASSERT_EQ(ebyte, abyte) << "Wire data mismatch at " << i << "th byte\n"
+ << " Actual: " << abyte << "\n"
+ << "Expected: " << ebyte << "\n";
+ }
+ EXPECT_EQ(expected_len, actual_len)
+ << "Wire data mismatch in length:\n"
+ << " Actual: " << actual_len << "\n"
+ << "Expected: " << expected_len << "\n";
+}
+
+} // unittests
+} // util
+} // isc
diff --git a/src/lib/util/unittests/wiredata.h b/src/lib/util/unittests/wiredata.h
new file mode 100644
index 0000000..fee555e
--- /dev/null
+++ b/src/lib/util/unittests/wiredata.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UTIL_UNITTESTS_WIREDATA_H
+#define UTIL_UNITTESTS_WIREDATA_H 1
+
+#include <cstddef>
+
+/// \file wiredata.h
+/// \brief Utilities for tests with wire data.
+///
+/// This utility provides convenient helper functions for unit tests using
+/// wire (binary) data.
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Compare two sets of binary data in a google test.
+///
+/// This method checks if the expected and actual data have the same length
+/// and all bytes are the same. If not, it reports the point of mismatch in
+/// the google test format.
+void matchWireData(const void* expected_data, std::size_t expected_len,
+ const void* actual_data, std::size_t actual_len);
+}
+}
+}
+
+#endif // UTIL_UNITTESTS_WIREDATA_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unlock_guard.h b/src/lib/util/unlock_guard.h
new file mode 100644
index 0000000..30be514
--- /dev/null
+++ b/src/lib/util/unlock_guard.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UNLOCK_GUARD_H
+#define UNLOCK_GUARD_H
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief Unlock Guard.
+///
+/// Acts as a reverse std::lock_guard.
+///
+/// @tparam Mutex a mutex object.
+template<typename Mutex>
+class UnlockGuard : public boost::noncopyable {
+public:
+ /// @brief Constructor.
+ ///
+ /// Unlock mutex object on constructor.
+ ///
+ /// @param lock the mutex used for unlocking and locking.
+ explicit UnlockGuard(Mutex& lock) : lock_(lock) {
+ lock_.unlock();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Lock mutex object on destructor.
+ ~UnlockGuard() {
+ lock_.lock();
+ }
+
+private:
+ /// @brief The mutex object.
+ Mutex& lock_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // UNLOCK_GUARD_H
diff --git a/src/lib/util/util.dox b/src/lib/util/util.dox
new file mode 100644
index 0000000..94769a1
--- /dev/null
+++ b/src/lib/util/util.dox
@@ -0,0 +1,93 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+ @page libutil libkea-util - Kea Utilities Library
+
+@section utilUtilities Utilities
+
+The utilities library (libkea-util) provides generic and Kea utilities:
+
+ - boost time: boost Posix time and duration to text conversions.
+
+ - buffer (header only): input and output buffers.
+
+ - csv file: comma-separated values (CSV) files.
+
+ - double: test utility for checking double equivalence (vs. strict
+ equality).
+
+ - encode: base16, base32, base64 and hexadecimal conversions.
+
+ - filename: filename manipulation (avoid dependency on boost).
+
+ - hash: Fowler-Noll-Vo 64 bit hash function.
+
+ - io: test utils for file descriptors and sockets.
+
+ - io utilities (header only): reads and writes integers from / to buffers.
+
+ - labeled values: labeled constants and label constant sets.
+
+ - multi threading manager (in the util library to be available in the
+ whole Kea code).
+
+ - optional: optional values.
+
+ - pid file: process id files.
+
+ - pointer util: test utility to compare smart pointers.
+
+ - process spawn.
+
+ - range utilities.
+
+ - read-write mutex (header only).
+
+ - signal set: signal handling (please use @c isc::asiolink::IOSignalSet
+ instead).
+
+ - staged values.
+
+ - state model: event-driven deterministic finite state machine.
+
+ - stop watch: to measure code execution time.
+
+ - string util: various string common tools.
+
+ - thread pool.
+
+ - time utilities: DNSSEC time conversions from and to text.
+
+ - unittests (directory): tools for google test unit tests.
+
+ - unlock guard: RAII helper to unlock a mutex in a limited scope.
+
+ - versioned csv file: csv files with multiple versions of file schema.
+
+ - watched socket (required as select() or poll() do not support condition
+ variables).
+
+ - watched threads: threads using ready, error and terminate watched socket.
+
+@section utilMTConsiderations Multi-Threading Consideration for Utilities
+
+By default utilities are not thread safe, for instance CSV files and
+qid random generators are not thread safe. Exceptions are:
+
+ - multi threading manager is thread safe.
+
+ - read-write mutex is thread safe.
+
+ - state model is thread safe.
+
+ - thread pool is thread safe.
+
+ - unlock guard is thread safe.
+
+ - watched threads are thread safe.
+
+*/
diff --git a/src/lib/util/versioned_csv_file.cc b/src/lib/util/versioned_csv_file.cc
new file mode 100644
index 0000000..8c48f66
--- /dev/null
+++ b/src/lib/util/versioned_csv_file.cc
@@ -0,0 +1,247 @@
+// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/versioned_csv_file.h>
+
+namespace isc {
+namespace util {
+
+VersionedCSVFile::VersionedCSVFile(const std::string& filename)
+ : CSVFile(filename), columns_(0), valid_column_count_(0),
+ minimum_valid_columns_(0), input_header_count_(0),
+ input_schema_state_(CURRENT) {
+}
+
+VersionedCSVFile::~VersionedCSVFile() {
+}
+
+void
+VersionedCSVFile::addColumn(const std::string& name,
+ const std::string& version,
+ const std::string& default_value) {
+ CSVFile::addColumn(name);
+ columns_.push_back(VersionedColumnPtr(new VersionedColumn(name, version,
+ default_value)));
+}
+
+void
+VersionedCSVFile::setMinimumValidColumns(const std::string& column_name) {
+ try {
+ int index = getColumnIndex(column_name);
+ minimum_valid_columns_ = index + 1;
+
+ } catch (...) {
+ isc_throw(VersionedCSVFileError,
+ "setMinimumValidColumns: " << column_name << " is not "
+ "defined");
+ }
+}
+
+size_t
+VersionedCSVFile::getMinimumValidColumns() const {
+ return (minimum_valid_columns_);
+}
+
+size_t
+VersionedCSVFile::getValidColumnCount() const {
+ return (valid_column_count_);
+}
+
+size_t
+VersionedCSVFile::getInputHeaderCount() const {
+ return (input_header_count_);
+}
+
+void
+VersionedCSVFile::open(const bool seek_to_end) {
+ if (getColumnCount() == 0) {
+ isc_throw(VersionedCSVFileError,
+ "no schema has been defined, cannot open CSV file :"
+ << getFilename());
+ }
+
+ CSVFile::open(seek_to_end);
+}
+
+void
+VersionedCSVFile::recreate() {
+ if (getColumnCount() == 0) {
+ isc_throw(VersionedCSVFileError,
+ "no schema has been defined, cannot create CSV file :"
+ << getFilename());
+ }
+
+ CSVFile::recreate();
+ // For new files they always match.
+ input_header_count_ = valid_column_count_ = getColumnCount();
+}
+
+VersionedCSVFile::InputSchemaState
+VersionedCSVFile::getInputSchemaState() const {
+ return (input_schema_state_);
+}
+
+bool
+VersionedCSVFile::needsConversion() const {
+ return (input_schema_state_ != CURRENT);
+}
+
+std::string
+VersionedCSVFile::getInputSchemaVersion() const {
+ if (getValidColumnCount() > 0) {
+ return (getVersionedColumn(getValidColumnCount() - 1)->version_);
+ }
+
+ return ("undefined");
+}
+
+std::string
+VersionedCSVFile::getSchemaVersion() const {
+ if (getColumnCount() > 0) {
+ return (getVersionedColumn(getColumnCount() - 1)->version_);
+ }
+
+ return ("undefined");
+}
+
+const VersionedColumnPtr&
+VersionedCSVFile::getVersionedColumn(const size_t index) const {
+ if (index >= getColumnCount()) {
+ isc_throw(isc::OutOfRange, "versioned column index " << index
+ << " out of range; CSV file : " << getFilename()
+ << " only has " << getColumnCount() << " columns ");
+ }
+
+ return (columns_[index]);
+}
+
+bool
+VersionedCSVFile::next(CSVRow& row) {
+ setReadMsg("success");
+ // Use base class to physical read the row, but skip its row
+ // validation
+ CSVFile::next(row, true);
+ if (row == CSVFile::EMPTY_ROW()) {
+ return(true);
+ }
+
+ bool row_valid = true;
+ switch(getInputSchemaState()) {
+ case CURRENT:
+ // All rows must match than the current schema
+ if (row.getValuesCount() != getColumnCount()) {
+ columnCountError(row, "must match current schema");
+ row_valid = false;
+ }
+ break;
+
+ case NEEDS_UPGRADE:
+ // The input header met the minimum column count but
+ // is less than the current schema so:
+ // Rows must not be shorter than the valid column count
+ // and not longer than the current schema
+ if (row.getValuesCount() < getValidColumnCount()) {
+ columnCountError(row, "too few columns to upgrade");
+ row_valid = false;
+ } else if (row.getValuesCount() > getColumnCount()) {
+ columnCountError(row, "too many columns to upgrade");
+ row_valid = false;
+ } else {
+ // Add any missing values
+ for (size_t index = row.getValuesCount();
+ index < getColumnCount(); ++index) {
+ row.append(columns_[index]->default_value_);
+ }
+ }
+ break;
+
+ case NEEDS_DOWNGRADE:
+ // The input header exceeded current schema so:
+ // Rows may be as long as input header but not shorter than
+ // the current schema
+ if (row.getValuesCount() < getColumnCount()) {
+ columnCountError(row, "too few columns to downgrade");
+ } else if (row.getValuesCount() > getInputHeaderCount()) {
+ columnCountError(row, "too many columns to downgrade");
+ } else {
+ // Toss any the extra columns
+ row.trim(row.getValuesCount() - getColumnCount());
+ }
+ break;
+ }
+
+ return (row_valid);
+}
+
+void
+VersionedCSVFile::columnCountError(const CSVRow& row,
+ const std::string& reason) {
+ std::ostringstream s;
+ s << "Invalid number of columns: "
+ << row.getValuesCount() << " in row: '" << row
+ << "', file: '" << getFilename() << "' : " << reason;
+ setReadMsg(s.str());
+}
+
+bool
+VersionedCSVFile::validateHeader(const CSVRow& header) {
+ if (getColumnCount() == 0) {
+ isc_throw(VersionedCSVFileError,
+ "cannot validate header, no schema has been defined");
+ }
+
+ input_header_count_ = header.getValuesCount();
+
+ // Iterate over the number of columns in the header, testing
+ // each against the defined column in the same position.
+ // If there is a mismatch, bail.
+ size_t i = 0;
+ for ( ; i < getInputHeaderCount() && i < getColumnCount(); ++i) {
+ if (getColumnName(i) != header.readAt(i)) {
+ std::ostringstream s;
+ s << " - header contains an invalid column: '"
+ << header.readAt(i) << "'";
+ setReadMsg(s.str());
+ return (false);
+ }
+ }
+
+ // If we found too few valid columns, then we cannot convert this
+ // file. It's too old, too corrupt, or not a Kea file.
+ if (i < getMinimumValidColumns()) {
+ std::ostringstream s;
+ s << " - header has only " << i << " valid column(s), "
+ << "it must have at least " << getMinimumValidColumns();
+ setReadMsg(s.str());
+ return (false);
+ }
+
+ // Remember the number of valid columns we found. When this number
+ // is less than the number of defined columns, then we have an older
+ // version of the lease file. We'll need this value to validate
+ // and upgrade data rows.
+ valid_column_count_ = i;
+
+ if (getValidColumnCount() < getColumnCount()) {
+ input_schema_state_ = NEEDS_UPGRADE;
+ } else if (getInputHeaderCount() > getColumnCount()) {
+ // If there are more values in the header than defined columns
+ // then, we'll drop the extra. This allows someone to attempt to
+ // downgrade if need be.
+ input_schema_state_ = NEEDS_DOWNGRADE;
+ std::ostringstream s;
+ s << " - header has " << getInputHeaderCount() - getColumnCount()
+ << " extra column(s), these will be ignored";
+ setReadMsg(s.str());
+ }
+
+ return (true);
+}
+
+} // end of isc::util namespace
+} // end of isc namespace
diff --git a/src/lib/util/versioned_csv_file.h b/src/lib/util/versioned_csv_file.h
new file mode 100644
index 0000000..cfd18d9
--- /dev/null
+++ b/src/lib/util/versioned_csv_file.h
@@ -0,0 +1,317 @@
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef VERSIONED_CSV_FILE_H
+#define VERSIONED_CSV_FILE_H
+
+#include <util/csv_file.h>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown when an error occurs during CSV file processing.
+class VersionedCSVFileError : public Exception {
+public:
+ VersionedCSVFileError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Contains the metadata for a single column in a file.
+class VersionedColumn {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name Name of the column.
+ /// @param version Text representation of the schema version in which
+ /// this column first appeared.
+ /// @param default_value The value the column should be assigned if it
+ /// is not present in a data row. It defaults to an empty string, ""
+ VersionedColumn(const std::string& name, const std::string& version,
+ const std::string& default_value = "")
+ : name_(name), version_(version), default_value_(default_value) {
+ };
+
+ /// @brief Destructor
+ virtual ~VersionedColumn(){};
+
+ /// @brief Name of the column.
+ std::string name_;
+
+ /// @brief Text representation of the schema version in which
+ /// this column first appeared.
+ std::string version_;
+
+ /// @brief default_value The value the column should be assigned if it
+ /// is not present in a data row.
+ std::string default_value_;
+};
+
+/// @brief Defines a smart pointer to VersionedColumn
+typedef boost::shared_ptr<VersionedColumn> VersionedColumnPtr;
+
+/// @brief Implements a CSV file that supports multiple versions of
+/// the file's "schema". This allows files with older schemas to be
+/// upgraded to newer schemas as they are being read. The file's schema
+/// is defined through a list of column descriptors, or @ref
+/// isc::util::VersionedColumn(s). Each descriptor contains metadata describing
+/// the column, consisting of the column's name, the version label in which
+/// the column was added to the schema, and a default value to be used if the
+/// column is missing from the file. Note that the column descriptors are
+/// defined in the order they occur in the file, when reading a row from left
+/// to right. This also assumes that when new version of the schema evolves,
+/// all new columns are added at the end of the row. In other words, the
+/// order of the columns reflects not only the order in which they occur
+/// in a row but also the order they were added to the schema. Conceptually,
+/// the entire list of columns defined constitutes the current schema. Earlier
+/// schema versions are therefore subsets of this list. Creating the schema
+/// is done by calling VersionedCSVfile::addColumn() for each column. Note
+/// that the schema must be defined prior to opening the file.
+///
+/// The first row of the file is always the header row and is a comma-separated
+/// list of the names of the column in the file. This row is used when
+/// opening the file via @ref VersionedCSVFile::open(), to identify its schema
+/// version so that it may be be read correctly. This is done by comparing
+/// the column found in the header to the columns defined in the schema. The
+/// columns must match both by name and the order in which they occur.
+///
+/// -# If there are fewer columns in the header than in the schema, the file
+/// is presumed to be an earlier schema version and will be upgraded as it is
+/// read. There is an ability to mark a specific column as being the minimum
+/// column which must be present, see @ref VersionedCSVFile::setMinimumValidColumns().
+/// If the header columns do not match up to this
+/// minimum column, the file is presumed to be too old to upgrade and the
+/// open will fail. A valid, upgradable file will have an input schema
+/// state of VersionedCSVFile::NEEDS_UPGRADE.
+///
+/// -# If there is a mismatch between a found column name and the column name
+/// defined for that position in the row, the file is presumed to be invalid
+/// and the open will fail.
+///
+/// -# If the content of the header matches exactly the columns defined in
+/// the schema, the file is considered to match the schema exactly and the
+/// input schema state will VersionedCSVFile::CURRENT.
+///
+/// -# If there columns in the header beyond all of the columns defined in
+/// the schema (i.e the schema is a subset of the header), then the file
+/// is presumed to be from a newer version of Kea and can be downgraded. The
+/// input schema state fo the file will be set to
+/// VersionedCSVFile::NEEDS_DOWNGRADE.
+///
+/// After successfully opening a file, rows are read one at a time via
+/// @ref VersionedCSVFile::next() and handled according to the input schema
+/// state. Each data row is expected to have at least the same number of
+/// columns as were found in the header. Any row which as fewer values is
+/// discarded as invalid. Similarly, any row which is found to have more
+/// values than were found in the header is discarded as invalid.
+///
+/// When upgrading a row, the values for each missing column is filled in
+/// with the default value specified by that column's descriptor. When
+/// downgrading a row, extraneous values are dropped from the row.
+///
+/// It is important to note that upgrading or downgrading a file does NOT
+/// alter the physical file itself. Rather the conversion occurs after the
+/// raw data has been read but before it is passed to caller.
+///
+/// Also note that there is currently no support for writing out a file in
+/// anything other than the current schema.
+class VersionedCSVFile : public CSVFile {
+public:
+
+ /// @brief Possible input file schema states.
+ /// Used to categorize the input file's schema, relative to the defined
+ /// schema.
+ enum InputSchemaState {
+ CURRENT,
+ NEEDS_UPGRADE,
+ NEEDS_DOWNGRADE
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param filename CSV file name.
+ VersionedCSVFile(const std::string& filename);
+
+ /// @brief Destructor
+ virtual ~VersionedCSVFile();
+
+ /// @brief Adds metadata for a single column to the schema.
+ ///
+ /// This method appends a new column description to the file's schema.
+ /// Note this does not cause anything to be written to the physical file.
+ /// The name of the column will be placed in the CSV header when new file
+ /// is created by calling @c recreate or @c open function.
+ ///
+ /// @param col_name Name of the column.
+ /// @param version Text representation of the schema version in which
+ /// this column first appeared.
+ /// @param default_value value the missing column should be given during
+ /// an upgrade. It defaults to an empty string, ""
+ ///
+ /// @throw CSVFileError if a column with the specified name exists.
+ void addColumn(const std::string& col_name, const std::string& version,
+ const std::string& default_value = "");
+
+ /// @brief Sets the minimum number of valid columns based on a given column
+ ///
+ /// @param column_name Name of the column which positionally represents
+ /// the minimum columns which must be present in a file and to be
+ /// considered valid.
+ void setMinimumValidColumns(const std::string& column_name);
+
+ /// @brief Returns the minimum number of columns which must be present
+ /// for the file to be considered valid.
+ size_t getMinimumValidColumns() const;
+
+ /// @brief Returns the number of columns found in the input header
+ size_t getInputHeaderCount() const;
+
+ /// @brief Returns the number of valid columns found in the header
+ /// For newly created files this will always match the number of defined
+ /// columns (i.e. getColumnCount()). For existing files, this will be
+ /// the number of columns in the header that match the defined columns.
+ /// When this number is less than getColumnCount() it means the input file
+ /// is from an earlier schema. This value is zero until the file has
+ /// been opened.
+ size_t getValidColumnCount() const;
+
+ /// @brief Opens existing file or creates a new one.
+ ///
+ /// This function will try to open existing file if this file has size
+ /// greater than 0. If the file doesn't exist or has size of 0, the
+ /// file is recreated. If the existing file has been opened, the header
+ /// is parsed and and validated against the schema.
+ /// By default, the data pointer in the file is set to the beginning of
+ /// the first data row. In order to retrieve the row contents the @c next
+ /// function should be called. If a @c seek_to_end parameter is set to
+ /// true, the file will be opened and the internal pointer will be set
+ /// to the end of file.
+ ///
+ /// @param seek_to_end A boolean value which indicates if the input and
+ /// output file pointer should be set at the end of file.
+ ///
+ /// @throw VersionedCSVFileError if schema has not been defined,
+ /// CSVFileError when IO operation fails, or header fails to validate.
+ virtual void open(const bool seek_to_end = false);
+
+ /// @brief Creates a new CSV file.
+ ///
+ /// The file creation will fail if there are no columns specified.
+ /// Otherwise, this function will write the header to the file.
+ /// In order to write rows to opened file, the @c append function
+ /// should be called.
+ ///
+ /// @throw VersionedCSVFileError if schema has not been defined
+ /// CSVFileError if an IO operation fails
+ virtual void recreate();
+
+ /// @brief Reads next row from the file file.
+ ///
+ /// This function will return the @c CSVRow object representing a
+ /// parsed row if parsing is successful. If the end of file has been
+ /// reached, the empty row is returned (a row containing no values).
+ ///
+ /// 1. If the row has fewer values than were found in the header it is
+ /// discarded as invalid.
+ ///
+ /// 2. If the row is found to have more values than are defined in the
+ /// schema it is discarded as invalid
+ ///
+ /// When a valid row has fewer than the defined number of columns, the
+ /// values for each missing column is filled in with the default value
+ /// specified by that column's descriptor.
+ ///
+ /// @param [out] row Object receiving the parsed CSV file.
+ ///
+ /// @return true if row has been read and validated; false if validation
+ /// failed.
+ bool next(CSVRow& row);
+
+ /// @brief Returns the schema version of the physical file
+ ///
+ /// @return text version of the schema found or string "undefined" if the
+ /// file has not been opened
+ std::string getInputSchemaVersion() const;
+
+ /// @brief text version of current schema supported by the file's metadata
+ ///
+ /// @return text version info assigned to the last column in the list of
+ /// defined column, or the string "undefined" if no columns have been
+ /// defined.
+ std::string getSchemaVersion() const;
+
+ /// @brief Fetch the column descriptor for a given index
+ ///
+ /// @param index index within the list of columns of the desired column
+ /// @return a pointer to the VersionedColumn at the given index
+ /// @throw OutOfRange exception if the index is invalid
+ const VersionedColumnPtr& getVersionedColumn(const size_t index) const;
+
+ /// @brief Fetches the state of the input file's schema
+ ///
+ /// Reflects that state of the input file's schema relative to the
+ /// defined schema as a enum, InputSchemaState.
+ ///
+ /// @return VersionedCSVFile::CURRENT if the input file schema matches
+ /// the defined schema, NEEDS_UPGRADE if the input file schema is older,
+ /// and NEEDS_DOWNGRADE if it is newer
+ enum InputSchemaState getInputSchemaState() const;
+
+ /// @brief Returns true if the input file schema state is not CURRENT
+ bool needsConversion() const;
+
+protected:
+
+ /// @brief Validates the header of a VersionedCSVFile
+ ///
+ /// This function is called internally when the reading in an existing
+ /// file. It parses the header row of the file, comparing each value
+ /// in succession against the defined list of columns. If the header
+ /// contains too few matching columns (i.e. less than @c
+ /// minimum_valid_columns_) or too many (more than the number of defined
+ /// columns), the file is presumed to be either too old, too new, or too
+ /// corrupt to process. Otherwise it retains the number of valid columns
+ /// found and deems the header valid.
+ ///
+ /// @param header A row holding a header.
+ /// @return true if header matches the columns; false otherwise.
+ virtual bool validateHeader(const CSVRow& header);
+
+ /// @brief Convenience method for adding an error message
+ ///
+ /// Constructs an error message indicating that the number of columns
+ /// in a given row are wrong and why, then adds it readMsg.
+ ///
+ /// @param row The row in error
+ /// @param reason An explanation as to why the row column count is wrong
+ void columnCountError(const CSVRow& row, const std::string& reason);
+
+private:
+ /// @brief Holds the collection of column descriptors
+ std::vector<VersionedColumnPtr> columns_;
+
+ /// @brief Number of valid columns present in input file. If this is less
+ /// than the number of columns defined, this implies the input file is
+ /// from an earlier version of the code.
+ size_t valid_column_count_;
+
+ /// @brief Minimum number of valid columns an input file must contain.
+ /// If an input file does not meet this number it cannot be upgraded.
+ size_t minimum_valid_columns_;
+
+ /// @brief The number of columns found in the input header row
+ /// This value represent the number of columns present, in the header
+ /// valid or otherwise.
+ size_t input_header_count_;
+
+ /// @brief The state of the input schema in relation to the current schema
+ enum InputSchemaState input_schema_state_;
+};
+
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // VERSIONED_CSV_FILE_H
diff --git a/src/lib/util/watch_socket.cc b/src/lib/util/watch_socket.cc
new file mode 100644
index 0000000..6ffb396
--- /dev/null
+++ b/src/lib/util/watch_socket.cc
@@ -0,0 +1,165 @@
+// Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file watch_socket.cc
+
+#include <config.h>
+
+//#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <util/watch_socket.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sstream>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+namespace isc {
+namespace util {
+
+
+const int WatchSocket::SOCKET_NOT_VALID;
+const uint32_t WatchSocket::MARKER;
+
+WatchSocket::WatchSocket()
+ : source_(SOCKET_NOT_VALID), sink_(SOCKET_NOT_VALID) {
+ // Open the pipe.
+ int fds[2];
+ if (pipe(fds)) {
+ const char* errstr = strerror(errno);
+ isc_throw(WatchSocketError, "Cannot construct pipe: " << errstr);
+ }
+
+ source_ = fds[1];
+ sink_ = fds[0];
+
+ if (fcntl(source_, F_SETFD, FD_CLOEXEC)) {
+ const char* errstr = strerror(errno);
+ isc_throw(WatchSocketError, "Cannot set source to close-on-exec: "
+ << errstr);
+ }
+
+ if (fcntl(sink_, F_SETFD, FD_CLOEXEC)) {
+ const char* errstr = strerror(errno);
+ isc_throw(WatchSocketError, "Cannot set sink to close-on-exec: "
+ << errstr);
+ }
+
+ if (fcntl(sink_, F_SETFL, O_NONBLOCK)) {
+ const char* errstr = strerror(errno);
+ isc_throw(WatchSocketError, "Cannot set sink to non-blocking: "
+ << errstr);
+ }
+}
+
+WatchSocket::~WatchSocket() {
+ closeSocket();
+}
+
+void
+WatchSocket::markReady() {
+ // Make sure it hasn't been orphaned! Otherwise we may get SIGPIPE. We
+ // use fcntl to check as select() on some systems may show it as ready to
+ // read.
+ if (fcntl(sink_, F_GETFL) < 0) {
+ closeSocket();
+ isc_throw(WatchSocketError, "WatchSocket markReady failed:"
+ " select_fd was closed!");
+ }
+
+ if (!isReady()) {
+ int nbytes = write (source_, &MARKER, sizeof(MARKER));
+ if (nbytes != sizeof(MARKER)) {
+ // If there's an error get the error message than close
+ // the pipe. This should ensure any further use of the socket
+ // or testing the fd with select_fd will fail.
+ const char* errstr = strerror(errno);
+ closeSocket();
+ isc_throw(WatchSocketError, "WatchSocket markReady failed:"
+ << " bytes written: " << nbytes << " : " << errstr);
+ }
+ }
+}
+
+bool
+WatchSocket::isReady() {
+ // Report it as not ready rather than error here.
+ if (sink_ == SOCKET_NOT_VALID) {
+ return (false);
+ }
+
+ // Use ioctl FIONREAD vs polling select as it is faster.
+ int len;
+ int result = ioctl(sink_, FIONREAD, &len);
+ // Return true only if read ready, treat error same as not ready.
+ return ((result == 0) && (len > 0));
+}
+
+void
+WatchSocket::clearReady() {
+ if (isReady()) {
+ uint32_t buf = 0;
+ int nbytes = read (sink_, &buf, sizeof(buf));
+ if ((nbytes != sizeof(MARKER) || (buf != MARKER))) {
+ // If there's an error get the error message than close
+ // the pipe. This should ensure any further use of the socket
+ // or testing the fd with select_fd will fail.
+ const char* errstr = strerror(errno);
+ closeSocket();
+ isc_throw(WatchSocketError, "WatchSocket clearReady failed: "
+ "bytes read: " << nbytes << " : "
+ "value read: " << buf << " error :" << errstr);
+ }
+ }
+}
+
+bool
+WatchSocket::closeSocket(std::string& error_string) {
+ std::ostringstream s;
+ // Close the pipe fds. Technically a close can fail (hugely unlikely)
+ // but there's no recovery for it either. If one does fail we log it
+ // and go on. Plus this is called by the destructor and no one likes
+ // destructors that throw.
+ if (source_ != SOCKET_NOT_VALID) {
+ if (close(source_)) {
+ // An error occurred.
+ s << "Could not close source: " << strerror(errno);
+ }
+
+ source_ = SOCKET_NOT_VALID;
+ }
+
+ if (sink_ != SOCKET_NOT_VALID) {
+ if (close(sink_)) {
+ // An error occurred.
+ if (error_string.empty()) {
+ s << "could not close sink: " << strerror(errno);
+ }
+ }
+
+ sink_ = SOCKET_NOT_VALID;
+ }
+
+ error_string = s.str();
+
+ // If any errors have been reported, return false.
+ return (error_string.empty() ? true : false);
+}
+
+void
+WatchSocket::closeSocket() {
+ std::string error_string;
+ closeSocket(error_string);
+}
+
+int
+WatchSocket::getSelectFd() {
+ return (sink_);
+}
+
+} // namespace isc::util
+} // namespace isc
diff --git a/src/lib/util/watch_socket.h b/src/lib/util/watch_socket.h
new file mode 100644
index 0000000..e6d65f6
--- /dev/null
+++ b/src/lib/util/watch_socket.h
@@ -0,0 +1,143 @@
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef WATCH_SOCKET_H
+#define WATCH_SOCKET_H
+
+/// @file watch_socket.h Defines the class, WatchSocket.
+
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class WatchSocketError : public isc::Exception {
+public:
+ WatchSocketError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Provides an IO "ready" semaphore for use with select() or poll()
+/// WatchSocket exposes a single open file descriptor, the "select-fd" which
+/// can be marked as being ready to read (i.e. !EWOULDBLOCK) and cleared
+/// (i.e. EWOULDBLOCK). The select-fd can be used with select(), poll(), or
+/// their variants alongside other file descriptors.
+///
+/// Internally, WatchSocket uses a pipe. The select-fd is the "read" end of
+/// pipe. To mark the socket as ready to read, an integer marker is written
+/// to the pipe. To clear the socket, the marker is read from the pipe. Note
+/// that WatchSocket will only write the marker if it is not already marked.
+/// This prevents the socket's pipe from filling endlessly.
+///
+/// @warning Because the read or "sink" side of the pipe is used as the select_fd,
+/// it is possible for that fd to be interfered with, albeit only from within the
+/// process space which owns it. Performing operations that may alter the fd's state
+/// such as close, read, or altering behavior flags with fcntl or ioctl can have
+/// unpredictable results. It is intended strictly use with functions such as select()
+/// poll() or their variants.
+class WatchSocket : public boost::noncopyable {
+public:
+ /// @brief Value used to signify an invalid descriptor.
+ static const int SOCKET_NOT_VALID = -1;
+ /// @brief Value written to the source when marking the socket as ready.
+ /// The value itself is arbitrarily chosen as one that is unlikely to occur
+ /// otherwise and easy to debug.
+ static const uint32_t MARKER = 0xDEADBEEF;
+
+ /// @brief Constructor
+ ///
+ /// Constructs an instance of the WatchSocket in the cleared (EWOULDBLOCK)
+ /// state.
+ WatchSocket();
+
+ /// @brief Destructor
+ ///
+ /// Closes all internal resources, including the select-fd.
+ virtual ~WatchSocket();
+
+ /// @brief Marks the select-fd as ready to read.
+ ///
+ /// Marks the socket as ready to read, if is not already so marked.
+ /// If an error occurs, closeSocket is called. This will force any further
+ /// use of the select_fd to fail rather than show the fd as READY. Such
+ /// an error is almost surely a programmatic error which has corrupted the
+ /// select_fd.
+ ///
+ /// @throw WatchSocketError if an error occurs marking the socket.
+ void markReady();
+
+ /// @brief Returns true the if socket is marked as ready.
+ ///
+ /// This method uses a non-blocking call to select() to test read state of the
+ /// select_fd. Rather than track what the status "should be" it tests the status.
+ /// This should eliminate conditions where the select-fd appear to be perpetually
+ /// ready.
+ /// @return Returns true if select_fd is not SOCKET_NOT_VALID and select() reports it
+ /// as !EWOULDBLOCK, otherwise it returns false.
+ /// This method is guaranteed NOT to throw.
+ bool isReady();
+
+ /// @brief Clears the socket's ready to read marker.
+ ///
+ /// Clears the socket if it is currently marked as ready to read.
+ /// If an error occurs, closeSocket is called. This will force any further
+ /// use of the select_fd to fail rather than show the fd as READY. Such
+ /// an error is almost surely a programmatic error which has corrupted the
+ /// select_fd.
+ ///
+ /// @throw WatchSocketError if an error occurs clearing the socket
+ /// marker.
+ void clearReady();
+
+ /// @brief Returns the file descriptor to use to monitor the socket.
+ ///
+ /// @note Using this file descriptor as anything other than an argument
+ /// to select() or similar methods can have unpredictable results.
+ ///
+ /// @return The file descriptor associated with read end of the socket's
+ /// pipe.
+ int getSelectFd();
+
+ /// @brief Closes the descriptors associated with the socket.
+ ///
+ /// This method is used to close the socket and capture errors that
+ /// may occur during this operation.
+ ///
+ /// @param [out] error_string Holds the error string if closing
+ /// the socket failed. It will hold empty string otherwise.
+ ///
+ /// @return true if the operation was successful, false otherwise.
+ bool closeSocket(std::string& error_string);
+
+private:
+
+ /// @brief Closes the descriptors associated with the socket.
+ ///
+ /// This method is called by the class destructor and it ignores
+ /// any errors that may occur while closing the sockets.
+ void closeSocket();
+
+ /// @brief The end of the pipe to which the marker is written
+ int source_;
+
+ /// @brief The end of the pipe from which the marker is read.
+ /// This is the value returned as the select-fd.
+ int sink_;
+};
+
+/// @brief Defines a smart pointer to an instance of a WatchSocket.
+typedef boost::shared_ptr<WatchSocket> WatchSocketPtr;
+
+} // namespace isc::util
+} // namespace isc
+
+#endif
diff --git a/src/lib/util/watched_thread.cc b/src/lib/util/watched_thread.cc
new file mode 100644
index 0000000..e9c3468
--- /dev/null
+++ b/src/lib/util/watched_thread.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/watched_thread.h>
+#include <signal.h>
+
+namespace isc {
+namespace util {
+
+void
+WatchedThread::start(const std::function<void()>& thread_main) {
+ clearReady(ERROR);
+ clearReady(READY);
+ clearReady(TERMINATE);
+ setErrorInternal("no error");
+ // Protect us against signals
+ sigset_t sset;
+ sigset_t osset;
+ sigemptyset(&sset);
+ sigaddset(&sset, SIGCHLD);
+ sigaddset(&sset, SIGINT);
+ sigaddset(&sset, SIGHUP);
+ sigaddset(&sset, SIGTERM);
+ pthread_sigmask(SIG_BLOCK, &sset, &osset);
+ try {
+ thread_.reset(new std::thread(thread_main));
+ } catch (...) {
+ // Restore signal mask.
+ pthread_sigmask(SIG_SETMASK, &osset, 0);
+ throw;
+ }
+ // Restore signal mask.
+ pthread_sigmask(SIG_SETMASK, &osset, 0);
+}
+
+int
+WatchedThread::getWatchFd(WatchType watch_type) {
+ return(sockets_[watch_type].getSelectFd());
+}
+
+void
+WatchedThread::markReady(WatchType watch_type) {
+ sockets_[watch_type].markReady();
+}
+
+bool
+WatchedThread::isReady(WatchType watch_type) {
+ return (sockets_[watch_type].isReady());
+}
+
+void
+WatchedThread::clearReady(WatchType watch_type) {
+ sockets_[watch_type].clearReady();
+}
+
+bool
+WatchedThread::shouldTerminate() {
+ if (sockets_[TERMINATE].isReady()) {
+ clearReady(TERMINATE);
+ return (true);
+ }
+
+ return (false);
+}
+
+void
+WatchedThread::stop() {
+ if (thread_) {
+ markReady(TERMINATE);
+ thread_->join();
+ thread_.reset();
+ }
+
+ clearReady(ERROR);
+ clearReady(READY);
+ setErrorInternal("thread stopped");
+}
+
+void
+WatchedThread::setErrorInternal(const std::string& error_msg) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ last_error_ = error_msg;
+}
+
+void
+WatchedThread::setError(const std::string& error_msg) {
+ setErrorInternal(error_msg);
+ markReady(ERROR);
+}
+
+std::string
+WatchedThread::getLastError() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (last_error_);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/watched_thread.h b/src/lib/util/watched_thread.h
new file mode 100644
index 0000000..47b7264
--- /dev/null
+++ b/src/lib/util/watched_thread.h
@@ -0,0 +1,145 @@
+// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef WATCHED_THREAD_H
+#define WATCHED_THREAD_H
+
+#include <util/watch_socket.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <functional>
+#include <mutex>
+#include <thread>
+
+namespace isc {
+namespace util {
+
+/// @brief Thread pointer type.
+typedef boost::shared_ptr<std::thread> ThreadPtr;
+
+/// @brief Provides a thread and controls for monitoring its activities
+///
+/// Given a "worker function", this class creates a thread which
+/// runs the function and provides the means to monitor the thread
+/// for "error" and "ready" conditions, and finally to stop the thread.
+/// It uses three WatchSockets: one to indicate an error, one to indicate
+/// data is ready, and a third to monitor as a shut-down command.
+class WatchedThread {
+public:
+ /// @brief Enumerates the list of watch sockets used to mark events
+ /// These are used as arguments to watch socket accessor methods.
+ enum WatchType {
+ ERROR = 0,
+ READY = 1,
+ TERMINATE = 2
+ };
+
+ /// @brief Constructor
+ WatchedThread(){};
+
+ /// @brief Virtual destructor
+ virtual ~WatchedThread(){}
+
+ /// @brief Fetches the fd of a watch socket
+ ///
+ /// @param watch_type indicates which watch socket
+ /// @return the watch socket's file descriptor
+ int getWatchFd(WatchType watch_type);
+
+ /// @brief Sets a watch socket state to ready
+ ///
+ /// @param watch_type indicates which watch socket to mark
+ void markReady(WatchType watch_type);
+
+ /// @brief Indicates if a watch socket state is ready
+ ///
+ /// @param watch_type indicates which watch socket to mark
+ /// @return true if the watch socket is ready, false otherwise
+ bool isReady(WatchType watch_type);
+
+ /// @brief Sets a watch socket state to not ready
+ ///
+ /// @param watch_type indicates which watch socket to clear
+ void clearReady(WatchType watch_type);
+
+ /// @brief Checks if the thread should terminate
+ ///
+ /// Performs a "one-shot" check of the terminate watch socket.
+ /// If it is ready, return true and then clear it, otherwise
+ /// return false.
+ ///
+ /// @return true if the terminate watch socket is ready
+ bool shouldTerminate();
+
+ /// @brief Creates and runs the thread.
+ ///
+ /// Creates the thread, passing into it the given function to run.
+ ///
+ /// @param thread_main function the thread should run
+ void start(const std::function<void()>& thread_main);
+
+ /// @brief Returns true if the thread is running
+ bool isRunning() {
+ return (thread_ != 0);
+ }
+
+ /// @brief Terminates the thread
+ ///
+ /// It marks the terminate watch socket ready, and then waits for the
+ /// thread to stop. At this point, the thread is defunct. This is
+ /// not done in the destructor to avoid race conditions.
+ void stop();
+
+ /// @brief Sets the error state
+ ///
+ /// This records the given error message and sets the error watch
+ /// socket to ready.
+ ///
+ /// @param error_msg to be set as last error
+ void setError(const std::string& error_msg);
+
+ /// @brief Fetches the error message text for the most recent error
+ ///
+ /// @return string containing the error message
+ std::string getLastError();
+
+private:
+
+ /// @brief Sets the error state thread safe
+ ///
+ /// This records the given error message
+ ///
+ /// @param error_msg to be set as last error
+ void setErrorInternal(const std::string& error_msg);
+
+ /// @brief Error message of the last error encountered
+ std::string last_error_;
+
+ /// @brief Mutex to protect internal state
+ std::mutex mutex_;
+
+ /// @brief WatchSockets that are used to communicate with the owning thread
+ /// There are three:
+ /// -# ERROR - Marked as ready by the thread when it experiences an error.
+ /// -# READY - Marked as ready by the thread when it needs attention for a normal event
+ /// (e.g. a thread used to receive data would mark READY when it has data available)
+ /// -# TERMINATE - Marked as ready by WatchedThread owner to instruct the thread to
+ /// terminate. Worker functions must monitor TERMINATE by periodically calling
+ /// @c shouldTerminate
+ WatchSocket sockets_[TERMINATE + 1];
+
+ /// @brief Current thread instance
+ ThreadPtr thread_ ;
+};
+
+/// @brief Defines a pointer to a WatchedThread
+typedef boost::shared_ptr<WatchedThread> WatchedThreadPtr;
+
+} // namespace util
+} // namespace isc
+
+#endif // WATCHED_THREAD_H