diff options
Diffstat (limited to 'src/lib/util')
138 files changed, 28944 insertions, 0 deletions
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am new file mode 100644 index 0000000..a81750a --- /dev/null +++ b/src/lib/util/Makefile.am @@ -0,0 +1,108 @@ +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 = 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 52: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 = \ + 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..586c82e --- /dev/null +++ b/src/lib/util/Makefile.in @@ -0,0 +1,1135 @@ +# 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_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_sysrepo.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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_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_SYSREPO = @HAVE_SYSREPO@ +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@ +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_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +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 = 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 52: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 = \ + 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/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..3e569b2 --- /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 +/// 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..eb0eb23 --- /dev/null +++ b/src/lib/util/encode/base_n.cc @@ -0,0 +1,480 @@ +// Copyright (C) 2010-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/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 iterator<input_iterator_tag, uint8_t> { +public: + 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 iterator<input_iterator_tag, char> { +public: + 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..ee32b5f --- /dev/null +++ b/src/lib/util/io/Makefile.in @@ -0,0 +1,890 @@ +# 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_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_sysrepo.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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_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_SYSREPO = @HAVE_SYSREPO@ +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@ +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_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +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..cc41fcf --- /dev/null +++ b/src/lib/util/io_utilities.h @@ -0,0 +1,156 @@ +// Copyright (C) 2011-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 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 uint8_t* 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 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 uint8_t* 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 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, uint8_t* 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)); +} + +} // 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..e019f98 --- /dev/null +++ b/src/lib/util/python/Makefile.in @@ -0,0 +1,531 @@ +# 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_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_sysrepo.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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_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_SYSREPO = @HAVE_SYSREPO@ +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@ +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_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +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..f8766d5 --- /dev/null +++ b/src/lib/util/readwrite_mutex.h @@ -0,0 +1,187 @@ +// 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 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..8f6bc65 --- /dev/null +++ b/src/lib/util/stopwatch_impl.cc @@ -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/. + +#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; + 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..3c1ee9c --- /dev/null +++ b/src/lib/util/stopwatch_impl.h @@ -0,0 +1,126 @@ +// 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 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. + /// + /// @todo Currently this function returns the duration as fractional + /// milliseconds. We may come up with something more sophisticated + /// in the future. + /// + /// @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..f5e9b76 --- /dev/null +++ b/src/lib/util/strutil.cc @@ -0,0 +1,453 @@ +// 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)); +} + +} // 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..53b4e29 --- /dev/null +++ b/src/lib/util/strutil.h @@ -0,0 +1,394 @@ +// 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 <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); +} + +} // 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..56aff6a --- /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 += 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..6924b4c --- /dev/null +++ b/src/lib/util/tests/Makefile.in @@ -0,0 +1,1688 @@ +# 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_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_sysrepo.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 \ + 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-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-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='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + 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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_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_SYSREPO = @HAVE_SYSREPO@ +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@ +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_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +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@ base32hex_unittest.cc 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-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-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-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-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/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 ("ˏO^O\\ˋ?ˊR,", 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 = ("noscape"); + 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ˎscape"); + 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,-bar"); + ASSERT_EQ(2, row0.getValuesCount()); + EXPECT_EQ("bar", row0.readAt(0)); + // Read the second column as-is and escaped + EXPECT_EQ("foo,-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,one,two", 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..6ecbf18 --- /dev/null +++ b/src/lib/util/tests/io_utilities_unittest.cc @@ -0,0 +1,160 @@ +// 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/. + +/// \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; + 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]; + + // 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]; + 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..f2a46d9 --- /dev/null +++ b/src/lib/util/tests/multi_threading_mgr_unittest.cc @@ -0,0 +1,626 @@ +// 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 <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 Verifies that the default mode is false (MT disabled). +TEST(MultiThreadingMgrTest, defaultMode) { + // MT should be disabled + EXPECT_FALSE(MultiThreadingMgr::instance().getMode()); +} + +/// @brief Verifies that the mode setter works. +TEST(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(MultiThreadingMgrTest, threadPool) { + // get the thread pool + EXPECT_NO_THROW(MultiThreadingMgr::instance().getThreadPool()); +} + +/// @brief Verifies that the thread pool size setter works. +TEST(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(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(MultiThreadingMgrTest, detectThreadCount) { + // detecting thread count should work + EXPECT_NE(MultiThreadingMgr::detectThreadCount(), 0); +} + +/// @brief Verifies that apply settings works. +TEST(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(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(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..28d6c99 --- /dev/null +++ b/src/lib/util/tests/stopwatch_unittest.cc @@ -0,0 +1,301 @@ +// Copyright (C) 2015-2017,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/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)); +} + +} // 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..8b5f78a --- /dev/null +++ b/src/lib/util/tests/strutil_unittest.cc @@ -0,0 +1,642 @@ +// 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/. + +#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("x01 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..fc339a4 --- /dev/null +++ b/src/lib/util/unittests/Makefile.am @@ -0,0 +1,33 @@ +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/io/libkea-util-io.la +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..45d581e --- /dev/null +++ b/src/lib/util/unittests/Makefile.in @@ -0,0 +1,822 @@ +# 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_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_sysrepo.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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_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_SYSREPO = @HAVE_SYSREPO@ +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@ +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_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +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 |