summaryrefslogtreecommitdiffstats
path: root/src/lib/config
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/config')
-rw-r--r--src/lib/config/Makefile.am85
-rw-r--r--src/lib/config/Makefile.in1037
-rw-r--r--src/lib/config/base_command_mgr.cc222
-rw-r--r--src/lib/config/base_command_mgr.h218
-rw-r--r--src/lib/config/client_connection.cc285
-rw-r--r--src/lib/config/client_connection.h159
-rw-r--r--src/lib/config/cmd_http_listener.cc165
-rw-r--r--src/lib/config/cmd_http_listener.h160
-rw-r--r--src/lib/config/cmd_response_creator.cc176
-rw-r--r--src/lib/config/cmd_response_creator.h132
-rw-r--r--src/lib/config/cmd_response_creator_factory.h63
-rw-r--r--src/lib/config/cmds_impl.h78
-rw-r--r--src/lib/config/command-socket.dox188
-rw-r--r--src/lib/config/command_mgr.cc667
-rw-r--r--src/lib/config/command_mgr.h94
-rw-r--r--src/lib/config/config_log.cc22
-rw-r--r--src/lib/config/config_log.h29
-rw-r--r--src/lib/config/config_messages.cc77
-rw-r--r--src/lib/config/config_messages.h42
-rw-r--r--src/lib/config/config_messages.mes143
-rw-r--r--src/lib/config/hooked_command_mgr.cc133
-rw-r--r--src/lib/config/hooked_command_mgr.h91
-rw-r--r--src/lib/config/tests/Makefile.am50
-rw-r--r--src/lib/config/tests/Makefile.in1108
-rw-r--r--src/lib/config/tests/client_connection_unittests.cc183
-rw-r--r--src/lib/config/tests/cmd_http_listener_unittests.cc980
-rw-r--r--src/lib/config/tests/cmd_response_creator_factory_unittests.cc71
-rw-r--r--src/lib/config/tests/cmd_response_creator_unittests.cc438
-rw-r--r--src/lib/config/tests/command_mgr_unittests.cc570
-rw-r--r--src/lib/config/tests/data_def_unittests_config.h.in7
-rw-r--r--src/lib/config/tests/run_unittests.cc18
-rw-r--r--src/lib/config/tests/testdata/Makefile.am59
-rw-r--r--src/lib/config/tests/testdata/Makefile.in560
-rw-r--r--src/lib/config/tests/testdata/data22_1.data9
-rw-r--r--src/lib/config/tests/testdata/data22_10.data11
-rw-r--r--src/lib/config/tests/testdata/data22_2.data8
-rw-r--r--src/lib/config/tests/testdata/data22_3.data8
-rw-r--r--src/lib/config/tests/testdata/data22_4.data8
-rw-r--r--src/lib/config/tests/testdata/data22_5.data8
-rw-r--r--src/lib/config/tests/testdata/data22_6.data10
-rw-r--r--src/lib/config/tests/testdata/data22_7.data10
-rw-r--r--src/lib/config/tests/testdata/data22_8.data10
-rw-r--r--src/lib/config/tests/testdata/data22_9.data11
-rw-r--r--src/lib/config/tests/testdata/data32_1.data3
-rw-r--r--src/lib/config/tests/testdata/data32_2.data3
-rw-r--r--src/lib/config/tests/testdata/data32_3.data3
-rw-r--r--src/lib/config/tests/testdata/data33_1.data7
-rw-r--r--src/lib/config/tests/testdata/data33_2.data7
-rw-r--r--src/lib/config/tests/testdata/data41_1.data12
-rw-r--r--src/lib/config/tests/testdata/data41_2.data16
-rw-r--r--src/lib/config/tests/testdata/spec1.spec6
-rw-r--r--src/lib/config/tests/testdata/spec10.spec13
-rw-r--r--src/lib/config/tests/testdata/spec11.spec13
-rw-r--r--src/lib/config/tests/testdata/spec12.spec13
-rw-r--r--src/lib/config/tests/testdata/spec13.spec13
-rw-r--r--src/lib/config/tests/testdata/spec14.spec13
-rw-r--r--src/lib/config/tests/testdata/spec15.spec13
-rw-r--r--src/lib/config/tests/testdata/spec16.spec7
-rw-r--r--src/lib/config/tests/testdata/spec17.spec17
-rw-r--r--src/lib/config/tests/testdata/spec18.spec12
-rw-r--r--src/lib/config/tests/testdata/spec19.spec13
-rw-r--r--src/lib/config/tests/testdata/spec2.spec83
-rw-r--r--src/lib/config/tests/testdata/spec20.spec18
-rw-r--r--src/lib/config/tests/testdata/spec21.spec7
-rw-r--r--src/lib/config/tests/testdata/spec22.spec114
-rw-r--r--src/lib/config/tests/testdata/spec23.spec18
-rw-r--r--src/lib/config/tests/testdata/spec24.spec18
-rw-r--r--src/lib/config/tests/testdata/spec25.spec7
-rw-r--r--src/lib/config/tests/testdata/spec26.spec6
-rw-r--r--src/lib/config/tests/testdata/spec27.spec121
-rw-r--r--src/lib/config/tests/testdata/spec28.spec7
-rw-r--r--src/lib/config/tests/testdata/spec29.spec35
-rw-r--r--src/lib/config/tests/testdata/spec3.spec13
-rw-r--r--src/lib/config/tests/testdata/spec30.spec45
-rw-r--r--src/lib/config/tests/testdata/spec31.spec63
-rw-r--r--src/lib/config/tests/testdata/spec32.spec72
-rw-r--r--src/lib/config/tests/testdata/spec33.spec50
-rw-r--r--src/lib/config/tests/testdata/spec34.spec14
-rw-r--r--src/lib/config/tests/testdata/spec35.spec15
-rw-r--r--src/lib/config/tests/testdata/spec36.spec17
-rw-r--r--src/lib/config/tests/testdata/spec37.spec7
-rw-r--r--src/lib/config/tests/testdata/spec38.spec17
-rw-r--r--src/lib/config/tests/testdata/spec39.spec21
-rw-r--r--src/lib/config/tests/testdata/spec4.spec12
-rw-r--r--src/lib/config/tests/testdata/spec40.spec21
-rw-r--r--src/lib/config/tests/testdata/spec41.spec35
-rw-r--r--src/lib/config/tests/testdata/spec42.spec17
-rw-r--r--src/lib/config/tests/testdata/spec5.spec12
-rw-r--r--src/lib/config/tests/testdata/spec6.spec12
-rw-r--r--src/lib/config/tests/testdata/spec7.spec5
-rw-r--r--src/lib/config/tests/testdata/spec8.spec3
-rw-r--r--src/lib/config/tests/testdata/spec9.spec13
-rw-r--r--src/lib/config/timeouts.h44
93 files changed, 9529 insertions, 0 deletions
diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am
new file mode 100644
index 0000000..a38f1bd
--- /dev/null
+++ b/src/lib/config/Makefile.am
@@ -0,0 +1,85 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-cfgclient.la
+libkea_cfgclient_la_SOURCES = cmds_impl.h
+libkea_cfgclient_la_SOURCES += base_command_mgr.cc base_command_mgr.h
+libkea_cfgclient_la_SOURCES += client_connection.cc client_connection.h
+libkea_cfgclient_la_SOURCES += command_mgr.cc command_mgr.h
+libkea_cfgclient_la_SOURCES += config_log.h config_log.cc
+libkea_cfgclient_la_SOURCES += config_messages.h config_messages.cc
+libkea_cfgclient_la_SOURCES += hooked_command_mgr.cc hooked_command_mgr.h
+libkea_cfgclient_la_SOURCES += timeouts.h
+libkea_cfgclient_la_SOURCES += cmd_http_listener.cc cmd_http_listener.h
+libkea_cfgclient_la_SOURCES += cmd_response_creator.cc cmd_response_creator.h
+libkea_cfgclient_la_SOURCES += cmd_response_creator_factory.h
+
+libkea_cfgclient_la_LIBADD = $(top_builddir)/src/lib/http/libkea-http.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_cfgclient_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+libkea_cfgclient_la_LDFLAGS = -no-undefined -version-info 51:0:0
+libkea_cfgclient_la_LDFLAGS += $(CRYPTO_LDFLAGS)
+
+# The message file should be in the distribution.
+EXTRA_DIST = config_messages.mes command-socket.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f config_messages.h config_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: config_messages.h config_messages.cc
+ @echo Message files regenerated
+
+config_messages.h config_messages.cc: config_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/config/config_messages.mes
+
+else
+
+messages config_messages.h config_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cfgclient_includedir = $(pkgincludedir)/config
+libkea_cfgclient_include_HEADERS = \
+ base_command_mgr.h \
+ client_connection.h \
+ cmds_impl.h \
+ command_mgr.h \
+ config_log.h \
+ config_messages.h \
+ hooked_command_mgr.h \
+ timeouts.h
+
diff --git a/src/lib/config/Makefile.in b/src/lib/config/Makefile.in
new file mode 100644
index 0000000..bbd2e3b
--- /dev/null
+++ b/src/lib/config/Makefile.in
@@ -0,0 +1,1037 @@
+# 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/config
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am \
+ $(libkea_cfgclient_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_cfgclient_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_cfgclient_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_libkea_cfgclient_la_OBJECTS = base_command_mgr.lo \
+ client_connection.lo command_mgr.lo config_log.lo \
+ config_messages.lo hooked_command_mgr.lo cmd_http_listener.lo \
+ cmd_response_creator.lo
+libkea_cfgclient_la_OBJECTS = $(am_libkea_cfgclient_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_cfgclient_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_cfgclient_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)/base_command_mgr.Plo \
+ ./$(DEPDIR)/client_connection.Plo \
+ ./$(DEPDIR)/cmd_http_listener.Plo \
+ ./$(DEPDIR)/cmd_response_creator.Plo \
+ ./$(DEPDIR)/command_mgr.Plo ./$(DEPDIR)/config_log.Plo \
+ ./$(DEPDIR)/config_messages.Plo \
+ ./$(DEPDIR)/hooked_command_mgr.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_cfgclient_la_SOURCES)
+DIST_SOURCES = $(libkea_cfgclient_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_cfgclient_include_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+lib_LTLIBRARIES = libkea-cfgclient.la
+libkea_cfgclient_la_SOURCES = cmds_impl.h base_command_mgr.cc \
+ base_command_mgr.h client_connection.cc client_connection.h \
+ command_mgr.cc command_mgr.h config_log.h config_log.cc \
+ config_messages.h config_messages.cc hooked_command_mgr.cc \
+ hooked_command_mgr.h timeouts.h cmd_http_listener.cc \
+ cmd_http_listener.h cmd_response_creator.cc \
+ cmd_response_creator.h cmd_response_creator_factory.h
+libkea_cfgclient_la_LIBADD = \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+libkea_cfgclient_la_LDFLAGS = -no-undefined -version-info 51:0:0 \
+ $(CRYPTO_LDFLAGS)
+
+# The message file should be in the distribution.
+EXTRA_DIST = config_messages.mes command-socket.dox
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cfgclient_includedir = $(pkgincludedir)/config
+libkea_cfgclient_include_HEADERS = \
+ base_command_mgr.h \
+ client_connection.h \
+ cmds_impl.h \
+ command_mgr.h \
+ config_log.h \
+ config_messages.h \
+ hooked_command_mgr.h \
+ timeouts.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/config/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/config/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-cfgclient.la: $(libkea_cfgclient_la_OBJECTS) $(libkea_cfgclient_la_DEPENDENCIES) $(EXTRA_libkea_cfgclient_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_cfgclient_la_LINK) -rpath $(libdir) $(libkea_cfgclient_la_OBJECTS) $(libkea_cfgclient_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base_command_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client_connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd_http_listener.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd_response_creator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hooked_command_mgr.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
+install-libkea_cfgclient_includeHEADERS: $(libkea_cfgclient_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_cfgclient_include_HEADERS)'; test -n "$(libkea_cfgclient_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_cfgclient_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_cfgclient_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_cfgclient_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_cfgclient_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_cfgclient_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_cfgclient_include_HEADERS)'; test -n "$(libkea_cfgclient_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_cfgclient_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_cfgclient_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)
+
+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)/base_command_mgr.Plo
+ -rm -f ./$(DEPDIR)/client_connection.Plo
+ -rm -f ./$(DEPDIR)/cmd_http_listener.Plo
+ -rm -f ./$(DEPDIR)/cmd_response_creator.Plo
+ -rm -f ./$(DEPDIR)/command_mgr.Plo
+ -rm -f ./$(DEPDIR)/config_log.Plo
+ -rm -f ./$(DEPDIR)/config_messages.Plo
+ -rm -f ./$(DEPDIR)/hooked_command_mgr.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_cfgclient_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)/base_command_mgr.Plo
+ -rm -f ./$(DEPDIR)/client_connection.Plo
+ -rm -f ./$(DEPDIR)/cmd_http_listener.Plo
+ -rm -f ./$(DEPDIR)/cmd_response_creator.Plo
+ -rm -f ./$(DEPDIR)/command_mgr.Plo
+ -rm -f ./$(DEPDIR)/config_log.Plo
+ -rm -f ./$(DEPDIR)/config_messages.Plo
+ -rm -f ./$(DEPDIR)/hooked_command_mgr.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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_cfgclient_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_cfgclient_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_cfgclient_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f config_messages.h config_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: config_messages.h config_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@config_messages.h config_messages.cc: config_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/config/config_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages config_messages.h config_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/config/base_command_mgr.cc b/src/lib/config/base_command_mgr.cc
new file mode 100644
index 0000000..ff1d2d8
--- /dev/null
+++ b/src/lib/config/base_command_mgr.cc
@@ -0,0 +1,222 @@
+// Copyright (C) 2017-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <config/base_command_mgr.h>
+#include <config/config_log.h>
+#include <cryptolink/crypto_hash.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <util/encode/hex.h>
+#include <util/buffer.h>
+#include <functional>
+#include <vector>
+
+using namespace isc::data;
+using namespace isc::hooks;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// Structure that holds registered hook indexes
+struct BaseCommandMgrHooks {
+ int hook_index_command_processed_; ///< index for "command_processe" hook point
+
+ /// Constructor that registers hook points for AllocationEngine
+ BaseCommandMgrHooks() {
+ hook_index_command_processed_ = HooksManager::registerHook("command_processed");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+BaseCommandMgrHooks Hooks;
+
+}; // anonymous namespace
+
+namespace isc {
+namespace config {
+
+BaseCommandMgr::BaseCommandMgr() {
+ registerCommand("list-commands", std::bind(&BaseCommandMgr::listCommandsHandler,
+ this, ph::_1, ph::_2));
+}
+
+void
+BaseCommandMgr::registerCommand(const std::string& cmd, CommandHandler handler) {
+ if (!handler) {
+ isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
+ }
+
+ HandlerContainer::const_iterator it = handlers_.find(cmd);
+ if (it != handlers_.end()) {
+ isc_throw(InvalidCommandName, "Handler for command '" << cmd
+ << "' is already installed.");
+ }
+
+ HandlersPair handlers;
+ handlers.handler = handler;
+ handlers_.insert(make_pair(cmd, handlers));
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_REGISTERED).arg(cmd);
+}
+
+void
+BaseCommandMgr::registerExtendedCommand(const std::string& cmd,
+ ExtendedCommandHandler handler) {
+ if (!handler) {
+ isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
+ }
+
+ HandlerContainer::const_iterator it = handlers_.find(cmd);
+ if (it != handlers_.end()) {
+ isc_throw(InvalidCommandName, "Handler for command '" << cmd
+ << "' is already installed.");
+ }
+
+ HandlersPair handlers;
+ handlers.extended_handler = handler;
+ handlers_.insert(make_pair(cmd, handlers));
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_EXTENDED_REGISTERED).arg(cmd);
+}
+
+void
+BaseCommandMgr::deregisterCommand(const std::string& cmd) {
+ if (cmd == "list-commands") {
+ isc_throw(InvalidCommandName,
+ "Can't uninstall internal command 'list-commands'");
+ }
+
+ HandlerContainer::iterator it = handlers_.find(cmd);
+ if (it == handlers_.end()) {
+ isc_throw(InvalidCommandName, "Handler for command '" << cmd
+ << "' not found.");
+ }
+ handlers_.erase(it);
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_DEREGISTERED).arg(cmd);
+}
+
+void
+BaseCommandMgr::deregisterAll() {
+
+ // No need to log anything here. deregisterAll is not used in production
+ // code, just in tests.
+ handlers_.clear();
+ registerCommand("list-commands",
+ std::bind(&BaseCommandMgr::listCommandsHandler, this, ph::_1, ph::_2));
+}
+
+isc::data::ConstElementPtr
+BaseCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
+ if (!cmd) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "Command processing failed: NULL command parameter"));
+ }
+
+ try {
+ ConstElementPtr arg;
+ std::string name = parseCommand(arg, cmd);
+
+ LOG_INFO(command_logger, COMMAND_RECEIVED).arg(name);
+
+ ConstElementPtr response = handleCommand(name, arg, cmd);
+
+ // If there any callouts for command-processed hook point call them
+ if (HooksManager::calloutsPresent(Hooks.hook_index_command_processed_)) {
+ // Commands are not associated with anything so there's no pre-existing
+ // callout.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Add the command name, arguments, and response to the callout context
+ callout_handle->setArgument("name", name);
+ callout_handle->setArgument("arguments", arg);
+ callout_handle->setArgument("response", response);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_command_processed_,
+ *callout_handle);
+
+ // Refresh the response from the callout context in case it was modified.
+ // @todo Should we allow this?
+ callout_handle->getArgument("response", response);
+ }
+
+ return (response);
+
+ } catch (const Exception& e) {
+ LOG_WARN(command_logger, COMMAND_PROCESS_ERROR2).arg(e.what());
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ std::string("Error during command processing: ")
+ + e.what()));
+ }
+}
+
+ConstElementPtr
+BaseCommandMgr::handleCommand(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd) {
+ auto it = handlers_.find(cmd_name);
+ if (it == handlers_.end()) {
+ // Ok, there's no such command.
+ return (createAnswer(CONTROL_RESULT_COMMAND_UNSUPPORTED,
+ "'" + cmd_name + "' command not supported."));
+ }
+
+ // Call the actual handler and return whatever it returned
+ if (it->second.handler) {
+ return (it->second.handler(cmd_name, params));
+ }
+ return (it->second.extended_handler(cmd_name, params, original_cmd));
+}
+
+isc::data::ConstElementPtr
+BaseCommandMgr::listCommandsHandler(const std::string& /* name */,
+ const isc::data::ConstElementPtr& ) {
+ using namespace isc::data;
+ ElementPtr commands = Element::createList();
+ for (HandlerContainer::const_iterator it = handlers_.begin();
+ it != handlers_.end(); ++it) {
+ commands->add(Element::create(it->first));
+ }
+ return (createAnswer(CONTROL_RESULT_SUCCESS, commands));
+}
+
+std::string
+BaseCommandMgr::getHash(const isc::data::ConstElementPtr& config) {
+
+ // Sanity.
+ if (!config) {
+ isc_throw(Unexpected, "BaseCommandMgr::getHash called with null");
+ }
+
+ // First, get the string representation.
+ std::string config_txt = config->str();
+ isc::util::OutputBuffer hash_data(0);
+ isc::cryptolink::digest(config_txt.c_str(),
+ config_txt.size(),
+ isc::cryptolink::HashAlgorithm::SHA256,
+ hash_data);
+
+ // Now we need to convert this to output buffer to vector, which can be accepted
+ // by encodeHex().
+ std::vector<uint8_t> hash;
+ hash.resize(hash_data.getLength());
+ if (hash.size() > 0) {
+ memmove(&hash[0], hash_data.getData(), hash.size());
+ }
+
+ // Now encode the value as base64 and we're done here.
+ return (isc::util::encode::encodeHex(hash));
+}
+
+} // namespace isc::config
+} // namespace isc
diff --git a/src/lib/config/base_command_mgr.h b/src/lib/config/base_command_mgr.h
new file mode 100644
index 0000000..916eebc
--- /dev/null
+++ b/src/lib/config/base_command_mgr.h
@@ -0,0 +1,218 @@
+// Copyright (C) 2017-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BASE_COMMAND_MGR_H
+#define BASE_COMMAND_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <functional>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace config {
+
+/// @brief Exception indicating that the handler specified is not valid
+class InvalidCommandHandler : public Exception {
+public:
+ InvalidCommandHandler(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception indicating that the command name is not valid
+class InvalidCommandName : public Exception {
+public:
+ InvalidCommandName(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Commands Manager, responsible for processing external commands.
+///
+/// Commands Manager is a generic interface for handling external commands.
+/// Commands are received over control sockets. Derivations of this class
+/// provide implementations of the control socket layers, e.g. unix domain
+/// sockets, TCP sockets etc. This base class merely provides methods to manage
+/// command handling functions, i.e. register commands, deregister commands.
+/// It also includes a @ref BaseCommandMgr::processCommand method which
+/// uses the command as an input and invokes appropriate handlers.
+///
+/// The commands and responses are formatted using JSON.
+/// See https://gitlab.isc.org/isc-projects/kea/wikis/designs/Stats-design
+/// for details.
+///
+/// Below is an example of the command using JSON format:
+/// @code
+/// {
+/// "command": "statistic-get",
+/// "arguments": {
+/// "name": "received-packets"
+/// }
+/// }
+/// @endcode
+///
+/// And the response is:
+///
+/// @code
+/// {
+/// "result": 0,
+/// "arguments": {
+/// "received-packets": [ [ 1234, "2015-04-15 12:34:45.123" ] ]
+/// }
+/// }
+/// @endcode
+///
+/// BaseCommandsMgr does not implement the commands (except one,
+/// "list-commands") itself, but rather provides an interface
+/// (see @ref registerCommand, @ref deregisterCommand, @ref processCommand)
+/// for other components to use it.
+class BaseCommandMgr {
+public:
+
+ /// @brief Defines command handler type
+ ///
+ /// Command handlers are expected to use this format.
+ ///
+ /// @param name name of the commands
+ /// @param params parameters specific to the command
+ /// @return response (created with createAnswer())
+ typedef std::function<isc::data::ConstElementPtr (const std::string& name,
+ const isc::data::ConstElementPtr& params)> CommandHandler;
+
+ /// @brief Defines extended command handler type.
+ ///
+ /// This command handler includes third parameter which holds the
+ /// entire command control message. The handler can retrieve
+ /// additional information from this parameter, e.g. 'service'.
+ ///
+ /// @param name name of the commands
+ /// @param params parameters specific to the command
+ /// @param original original control command.
+ /// @return response (created with createAnswer())
+ typedef std::function<isc::data::ConstElementPtr (const std::string& name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original)> ExtendedCommandHandler;
+
+ /// @brief Constructor.
+ ///
+ /// Registers hookpoint "command-processed"
+ /// Registers "list-commands" command.
+ BaseCommandMgr();
+
+ /// @brief Destructor.
+ virtual ~BaseCommandMgr() { };
+
+ /// @brief Triggers command processing.
+ ///
+ /// This method processes specified command. The command is specified using
+ /// a single Element. See @ref BaseCommandMgr for description of its syntax.
+ /// After the command has been handled, callouts for the hook point,
+ /// "command-processed" will be invoked.
+ ///
+ /// This method is virtual so it can be overridden in derived classes to
+ /// pre-process command and post-process response if necessary.
+ ///
+ /// This method is an entry point for dealing with a command. Internally
+ /// it calls @c BaseCommandMgr::handleCommand.
+ ///
+ /// @param cmd Pointer to the data element representing command in JSON
+ /// format.
+ virtual isc::data::ConstElementPtr
+ processCommand(const isc::data::ConstElementPtr& cmd);
+
+ /// @brief Registers specified command handler for a given command
+ ///
+ /// @param cmd Name of the command to be handled.
+ /// @param handler Pointer to the method that will handle the command.
+ void registerCommand(const std::string& cmd, CommandHandler handler);
+
+ /// @brief Registers specified command handler for a given command.
+ ///
+ /// This variant of the method uses extended command handler which, besides
+ /// command name and arguments, also has a third parameter 'original_cmd'
+ /// in its signature. Such handlers can retrieve additional parameters from
+ /// the command, e.g. 'service' indicating where the command should be
+ /// routed.
+ ///
+ /// @param cmd Name of the command to be handled.
+ /// @param handler Pointer to the method that will handle the command.
+ void registerExtendedCommand(const std::string& cmd,
+ ExtendedCommandHandler handler);
+
+ /// @brief Deregisters specified command handler.
+ ///
+ /// @param cmd Name of the command that's no longer handled.
+ void deregisterCommand(const std::string& cmd);
+
+ /// @brief Auxiliary method that removes all installed commands.
+ ///
+ /// The only unwipeable method is list-commands, which is internally
+ /// handled at all times.
+ void deregisterAll();
+
+ /// @brief returns a hash of a given Element structure
+ ///
+ /// The hash is currently implemented as SHA256 on the string
+ // representation of the structure.
+ ///
+ /// @param config typically full config, but hash can be calculated on any structure
+ /// @return hash of string representation
+ static std::string getHash(const isc::data::ConstElementPtr& config);
+
+protected:
+
+ /// @brief Handles the command having a given name and arguments.
+ ///
+ /// This method can be overridden in the derived classes to provide
+ /// custom logic for processing commands. For example, the
+ /// @ref HookedCommandMgr extends this method to delegate commands
+ /// processing to a hook library.
+ ///
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Pointer to the entire command received. It may
+ /// be sometimes useful to retrieve additional parameters from this
+ /// command.
+ ///
+ /// @return Pointer to the const data element representing response
+ /// to a command.
+ virtual isc::data::ConstElementPtr
+ handleCommand(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd);
+
+ struct HandlersPair {
+ CommandHandler handler;
+ ExtendedCommandHandler extended_handler;
+ };
+
+ /// @brief Type of the container for command handlers.
+ typedef std::map<std::string, HandlersPair> HandlerContainer;
+
+ /// @brief Container for command handlers.
+ HandlerContainer handlers_;
+
+private:
+
+ /// @brief 'list-commands' command handler.
+ ///
+ /// This method implements command 'list-commands'. It returns a list of all
+ /// currently supported commands.
+ ///
+ /// @param name Name of the command (should always be 'list-commands').
+ /// @param params Additional parameters (ignored).
+ ///
+ /// @return Pointer to the structure that includes all currently supported
+ /// commands.
+ isc::data::ConstElementPtr
+ listCommandsHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/client_connection.cc b/src/lib/config/client_connection.cc
new file mode 100644
index 0000000..6217c1a
--- /dev/null
+++ b/src/lib/config/client_connection.cc
@@ -0,0 +1,285 @@
+// Copyright (C) 2017-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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/unix_domain_socket.h>
+#include <cc/json_feed.h>
+#include <config/client_connection.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <array>
+#include <functional>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace config {
+
+/// @brief Implementation of the @ref ClientConnection.
+class ClientConnectionImpl : public boost::enable_shared_from_this<ClientConnectionImpl> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit ClientConnectionImpl(IOService& io_service);
+
+ /// @brief This method schedules timer or reschedules existing timer.
+ ///
+ /// @param handler Pointer to the user supplied callback function which
+ /// should be invoked when transaction completes or when an error has
+ /// occurred during the transaction.
+ void scheduleTimer(ClientConnection::Handler handler);
+
+ /// @brief Starts asynchronous transaction with a remote endpoint.
+ ///
+ /// See @ref ClientConnection::start documentation for the details.
+ ///
+ /// @param socket_path Path to the socket description that the server
+ /// is bound to.
+ /// @param command Control command to be sent to the server.
+ /// @param handler Pointer to the user supplied callback function which
+ /// should be invoked when transaction completes or when an error has
+ /// occurred during the transaction.
+ /// @param timeout Connection timeout in milliseconds.
+ void start(const ClientConnection::SocketPath& socket_path,
+ const ClientConnection::ControlCommand& command,
+ ClientConnection::Handler handler,
+ const ClientConnection::Timeout& timeout);
+
+ /// @brief Closes the socket.
+ void stop();
+
+ /// @brief Starts asynchronous send.
+ ///
+ /// This method may be called multiple times internally when the command
+ /// is large and can't be sent all at once.
+ ///
+ /// @param buffer Pointer to the buffer holding input data.
+ /// @param length Length of the data in the input buffer.
+ /// @param handler User supplied callback invoked after the chunk of data
+ /// has been sent.
+ void doSend(const void* buffer, const size_t length,
+ ClientConnection::Handler handler);
+
+ /// @brief Starts asynchronous receive from the server.
+ ///
+ /// This method may be called multiple times internally if the response
+ /// is large. The @ref JSONFeed instance is used to detect the boundaries
+ /// of the command within the stream. Once the entire command has been
+ /// received the user callback is invoked and the instance of the
+ /// @ref JSONFeed is returned.
+ ///
+ /// @param handler User supplied callback.
+ void doReceive(ClientConnection::Handler handler);
+
+ /// @brief Terminates the connection and invokes a user callback indicating
+ /// an error.
+ ///
+ /// @param ec Error code.
+ /// @param handler User callback.
+ void terminate(const boost::system::error_code& ec,
+ ClientConnection::Handler handler);
+
+ /// @brief Callback invoked when the timeout occurs.
+ ///
+ /// It calls @ref terminate with the @c boost::asio::error::timed_out.
+ void timeoutCallback(ClientConnection::Handler handler);
+
+private:
+
+ /// @brief Unix domain socket used for communication with a server.
+ UnixDomainSocket socket_;
+
+ /// @brief Pointer to the @ref JSONFeed holding a response.
+ ///
+ ///It may be a null pointer until some part of a response has been received.
+ JSONFeedPtr feed_;
+
+ /// @brief Holds the entire command being transmitted over the unix
+ /// socket.
+ std::string current_command_;
+
+ /// @brief Buffer into which chunks of the response are received.
+ std::array<char, 32768> read_buf_;
+
+ /// @brief Instance of the interval timer protecting against timeouts.
+ IntervalTimer timer_;
+
+ /// @brief Timeout value used for the timer.
+ long timeout_;
+};
+
+ClientConnectionImpl::ClientConnectionImpl(IOService& io_service)
+ : socket_(io_service), feed_(), current_command_(), timer_(io_service),
+ timeout_(0) {
+}
+
+void
+ClientConnectionImpl::scheduleTimer(ClientConnection::Handler handler) {
+ if (timeout_ > 0) {
+ timer_.setup(std::bind(&ClientConnectionImpl::timeoutCallback,
+ this, handler),
+ timeout_, IntervalTimer::ONE_SHOT);
+ }
+}
+
+void
+ClientConnectionImpl::start(const ClientConnection::SocketPath& socket_path,
+ const ClientConnection::ControlCommand& command,
+ ClientConnection::Handler handler,
+ const ClientConnection::Timeout& timeout) {
+ // Start the timer protecting against timeouts.
+ timeout_ = timeout.timeout_;
+ scheduleTimer(handler);
+
+ // Store the command in the class member to make sure it is valid
+ // the entire time.
+ current_command_.assign(command.control_command_);
+
+ // Pass self to lambda to make sure that the instance of this class
+ // lives as long as the lambda is held for async connect.
+ auto self(shared_from_this());
+ // Start asynchronous connect. This will return immediately.
+ socket_.asyncConnect(socket_path.socket_path_,
+ [this, self, command, handler](const boost::system::error_code& ec) {
+ // We failed to connect so we can't proceed. Simply clean up
+ // and invoke the user callback to signal an error.
+ if (ec) {
+ // This doesn't throw.
+ terminate(ec, handler);
+
+ } else {
+ // Connection successful. Transmit the command to the remote
+ // endpoint asynchronously.
+ doSend(current_command_.c_str(), current_command_.length(),
+ handler);
+ }
+ });
+}
+
+void
+ClientConnectionImpl::doSend(const void* buffer, const size_t length,
+ ClientConnection::Handler handler) {
+ // Pass self to lambda to make sure that the instance of this class
+ // lives as long as the lambda is held for async send.
+ auto self(shared_from_this());
+ // Start asynchronous transmission of the command. This will return
+ // immediately.
+ socket_.asyncSend(buffer, length,
+ [this, self, buffer, length, handler]
+ (const boost::system::error_code& ec, size_t bytes_transferred) {
+ // An error has occurred while sending. Close the connection and
+ // signal an error.
+ if (ec) {
+ // This doesn't throw.
+ terminate(ec, handler);
+
+ } else {
+ // Sending is in progress, so push back the timeout.
+ scheduleTimer(handler);
+
+ // If the number of bytes we have managed to send so far is
+ // lower than the amount of data we're trying to send, we
+ // have to schedule another send to deliver the rest of
+ // the data.
+ if (bytes_transferred < length) {
+ doSend(static_cast<const char*>(buffer) + bytes_transferred,
+ length - bytes_transferred, handler);
+
+ } else {
+ // We have sent all the data. Start receiving a response.
+ doReceive(handler);
+ }
+ }
+ });
+}
+
+void
+ClientConnectionImpl::doReceive(ClientConnection::Handler handler) {
+ // Pass self to lambda to make sure that the instance of this class
+ // lives as long as the lambda is held for async receive.
+ auto self(shared_from_this());
+ socket_.asyncReceive(&read_buf_[0], read_buf_.size(),
+ [this, self, handler]
+ (const boost::system::error_code& ec, size_t length) {
+ // An error has occurred while receiving the data. Close the connection
+ // and signal an error.
+ if (ec) {
+ // This doesn't throw.
+ terminate(ec, handler);
+
+ } else {
+ // Receiving is in progress, so push back the timeout.
+ scheduleTimer(handler);
+
+ std::string x(&read_buf_[0], length);
+ // Lazy initialization of the JSONFeed. The feed will be "parsing"
+ // received JSON stream and will detect when the whole response
+ // has been received.
+ if (!feed_) {
+ feed_.reset(new JSONFeed());
+ feed_->initModel();
+ }
+ // Put everything we have received so far into the feed and process
+ // the data.
+ feed_->postBuffer(&read_buf_[0], length);
+ feed_->poll();
+ // If the feed indicates that only a part of the response has been
+ // received, schedule another receive to get more data.
+ if (feed_->needData()) {
+ doReceive(handler);
+
+ } else {
+ // We have received the entire response, let's call the handler
+ // and indicate success.
+ terminate(ec, handler);
+ }
+ }
+ });
+}
+
+void
+ClientConnectionImpl::terminate(const boost::system::error_code& ec,
+ ClientConnection::Handler handler) {
+ try {
+ timer_.cancel();
+ socket_.close();
+ current_command_.clear();
+ handler(ec, feed_);
+
+ } catch (...) {
+ // None of these operations should throw. In particular, the handler
+ // should not throw but if it has been misimplemented, we want to make
+ // sure we don't emit any exceptions from here.
+ }
+}
+
+void
+ClientConnectionImpl::timeoutCallback(ClientConnection::Handler handler) {
+ // Timeout has occurred. The remote server didn't provide the entire
+ // response within the given time frame. Let's close the connection
+ // and signal the timeout.
+ terminate(boost::asio::error::timed_out, handler);
+}
+
+ClientConnection::ClientConnection(asiolink::IOService& io_service)
+ : impl_(new ClientConnectionImpl(io_service)) {
+}
+
+void
+ClientConnection::start(const ClientConnection::SocketPath& socket_path,
+ const ClientConnection::ControlCommand& command,
+ ClientConnection::Handler handler,
+ const ClientConnection::Timeout& timeout) {
+ impl_->start(socket_path, command, handler, timeout);
+}
+
+
+} // end of namespace config
+} // end of namespace isc
diff --git a/src/lib/config/client_connection.h b/src/lib/config/client_connection.h
new file mode 100644
index 0000000..6321f34
--- /dev/null
+++ b/src/lib/config/client_connection.h
@@ -0,0 +1,159 @@
+// Copyright (C) 2017-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 CLIENT_CONNECTION_H
+#define CLIENT_CONNECTION_H
+
+#include <asiolink/io_service.h>
+#include <cc/json_feed.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+
+namespace isc {
+namespace config {
+
+class ClientConnectionImpl;
+
+/// @brief Represents client side connection over the unix domain socket.
+///
+/// This class represents a client side connection between the controlling
+/// client and the server exposing control API over a unix domain socket.
+/// In particular, this class is used by the Kea Control Agent to establish
+/// connections with respective Kea services to forward received commands.
+/// As of Kea 1.2 the servers can handle a single connection at the time.
+/// In the future, we're planning to support multiple simulatenous connections.
+/// In this case, each connection will be handled by a unique instance of the
+/// @ref ClientConnection class.
+///
+/// The @ref ClientConnection supports asynchronous connections. A caller
+/// creates an instance of the @ref ClientConnection and calls
+/// @ref ClientConnection::start to start asynchronous communication with
+/// a remote server. The caller provides a pointer to the callback function
+/// (handler) which will be called when the communication with the server
+/// completes, i.e. the command is sent to the server and the response
+/// from the server is received. If an error occurs, the callback is
+/// invoked with an error code indicating a reason for the failure.
+///
+/// The documentation of the @ref ClientConnection::start explains the
+/// sequence of operations performed by this class.
+///
+/// Even though the @ref ClientConnection is asynchronous in nature, it
+/// can also be used in cases requiring synchronous communication. As it
+/// has been already mentioned, the servers in Kea 1.2 do not support
+/// multiple concurrent connections. The following pseudo code demonstrates
+/// how to perform synchronous transaction using this class.
+///
+/// @code
+/// IOService io_service;
+/// ClientConnection conn(io_service);
+/// bool cb_invoked = false;
+/// conn.start(ClientConnection::SocketPath("/tmp/kea.sock"),
+/// ClientConnection::ControlCommand(command),
+/// [this, &cb_invoked](const boost::system::error_code& ec,
+/// const ConstJSONFeedPtr& feed) {
+/// cb_invoked = true;
+/// if (ec) {
+/// ... handle error here ...
+/// } else {
+/// ... use feed to retrieve the response ...
+/// }
+/// }
+/// );
+/// while (!cb_invoked) {
+/// io_service.run_one();
+/// }
+/// @endcode
+///
+class ClientConnection {
+public:
+
+ /// @name Structures used for strong typing.
+ ///
+ //@{
+
+ /// @brief Encapsulates socket path.
+ struct SocketPath {
+ explicit SocketPath(const std::string& socket_path)
+ : socket_path_(socket_path) { }
+
+ std::string socket_path_;
+ };
+
+ /// @brief Encapsulates control command.
+ struct ControlCommand {
+ explicit ControlCommand(const std::string control_command)
+ : control_command_(control_command) { }
+
+ std::string control_command_;
+ };
+
+ /// @brief Encapsulates timeout value.
+ struct Timeout {
+ explicit Timeout(const long timeout)
+ : timeout_(timeout) { }
+
+ long timeout_;
+ };
+
+ //@}
+
+ /// @brief Type of the callback invoked when the communication with
+ /// the server is complete or an error has occurred.
+ typedef std::function<void(const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& feed)> Handler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit ClientConnection(asiolink::IOService& io_service);
+
+ /// @brief Starts asynchronous transaction with a remote endpoint.
+ ///
+ /// Starts asynchronous connection with the remote endpoint. If the
+ /// connection is successful, the control command is asynchronously
+ /// sent to the remote endpoint. When the entire command has been sent,
+ /// the response is read asynchronously, possibly in multiple chunks.
+ ///
+ /// The timeout is specified in milliseconds. The corresponding timer
+ /// measures the connection idle time. If the transaction is progressing,
+ /// the timer is updated accordingly. If the connection idle time is
+ /// longer than the timeout value the connection is closed and the
+ /// callback is called with the error code of
+ /// @c boost::asio::error::timed_out.
+ ///
+ /// In other cases, the callback is called with the error code returned
+ /// by the boost asynchronous operations. If the transaction is successful
+ /// the 'success' status is indicated with the error code. In addition
+ /// the instance of the @ref JSONFeed is returned to the caller. It can
+ /// be used to retrieve parsed response from the server. Note that the
+ /// response may still be malformed, even if no error is signalled in
+ /// the handler. The @ref JSONFeed::toElement will return a parsing
+ /// error if the JSON appears to be malformed.
+ ///
+ /// @param socket_path Path to the socket description that the server
+ /// is bound to.
+ /// @param command Control command to be sent to the server.
+ /// @param handler Pointer to the user supplied callback function which
+ /// should be invoked when transaction completes or when an error has
+ /// occurred during the transaction.
+ /// @param timeout Connection timeout in milliseconds.
+ void start(const SocketPath& socket_path, const ControlCommand& command,
+ Handler handler, const Timeout& timeout = Timeout(5000));
+
+private:
+
+ /// @brief Pointer to the implementation.
+ boost::shared_ptr<ClientConnectionImpl> impl_;
+
+};
+
+/// @brief Type of the pointer to the @ref ClientConnection object.
+typedef boost::shared_ptr<ClientConnection> ClientConnectionPtr;
+
+} // end of namespace config
+} // end of namespace isc
+
+#endif // CLIENT_CONNECTION_H
diff --git a/src/lib/config/cmd_http_listener.cc b/src/lib/config/cmd_http_listener.cc
new file mode 100644
index 0000000..aa917c7
--- /dev/null
+++ b/src/lib/config/cmd_http_listener.cc
@@ -0,0 +1,165 @@
+// Copyright (C) 2021-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <asiolink/io_service.h>
+#include <cmd_http_listener.h>
+#include <cmd_response_creator_factory.h>
+#include <config_log.h>
+#include <config/timeouts.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/pointer_cast.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::util;
+
+namespace isc {
+namespace config {
+
+CmdHttpListener::CmdHttpListener(const IOAddress& address, const uint16_t port,
+ const uint16_t thread_pool_size /* = 1 */,
+ TlsContextPtr context /* = () */)
+ : address_(address), port_(port), thread_io_service_(), http_listener_(),
+ thread_pool_size_(thread_pool_size), thread_pool_(),
+ tls_context_(context) {
+}
+
+CmdHttpListener::~CmdHttpListener() {
+ stop();
+}
+
+void
+CmdHttpListener::start() {
+ // We must be in multi-threading mode.
+ if (!MultiThreadingMgr::instance().getMode()) {
+ isc_throw(InvalidOperation, "CmdHttpListener cannot be started"
+ " when multi-threading is disabled");
+ }
+
+ // Punt if we're already started.
+ if (!isStopped()) {
+ isc_throw(InvalidOperation, "CmdHttpListener already started!");
+ }
+
+ try {
+ // Create a new IOService.
+ thread_io_service_.reset(new IOService());
+
+ // Create the response creator factory first. It will be used to
+ // generate response creators. Each response creator will be
+ // used to generate the answer to specific request.
+ HttpResponseCreatorFactoryPtr rcf(new CmdResponseCreatorFactory());
+
+ // Create the HTTP listener. It will open up a TCP socket and be
+ // prepared to accept incoming connections.
+ http_listener_.reset(new HttpListener(*thread_io_service_, address_,
+ port_, tls_context_, rcf,
+ HttpListener::RequestTimeout(TIMEOUT_AGENT_RECEIVE_COMMAND),
+ HttpListener::IdleTimeout(TIMEOUT_AGENT_IDLE_CONNECTION_TIMEOUT)));
+
+ // Instruct the HTTP listener to actually open socket, install
+ // callback and start listening.
+ http_listener_->start();
+
+ // Create the thread pool with immediate start.
+ thread_pool_.reset(new IoServiceThreadPool(thread_io_service_, thread_pool_size_));
+
+ // OK, seems like we're good to go.
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HTTP_LISTENER_STARTED)
+ .arg(thread_pool_size_)
+ .arg(address_)
+ .arg(port_)
+ .arg(tls_context_ ? "true" : "false");
+ } catch (const std::exception& ex) {
+ thread_io_service_.reset();
+ http_listener_.reset();
+ thread_pool_.reset();
+ isc_throw(Unexpected, "CmdHttpListener::run failed: " << ex.what());
+ }
+}
+
+void
+CmdHttpListener::checkPermissions() {
+ if (thread_pool_) {
+ thread_pool_->checkPausePermissions();
+ }
+}
+
+void
+CmdHttpListener::pause() {
+ if (thread_pool_) {
+ thread_pool_->pause();
+ }
+}
+
+void
+CmdHttpListener::resume() {
+ if (thread_pool_) {
+ thread_pool_->run();
+ }
+}
+
+void
+CmdHttpListener::stop() {
+ // Nothing to do.
+ if (!thread_io_service_) {
+ return;
+ }
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HTTP_LISTENER_STOPPING)
+ .arg(address_)
+ .arg(port_);
+
+ // Stop the thread pool.
+ thread_pool_->stop();
+
+ // Get rid of the listener.
+ http_listener_.reset();
+
+ // Ditch the IOService.
+ thread_io_service_.reset();
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HTTP_LISTENER_STOPPED)
+ .arg(address_)
+ .arg(port_);
+}
+
+bool
+CmdHttpListener::isRunning() {
+ if (thread_pool_) {
+ return (thread_pool_->isRunning());
+ }
+
+ return (false);
+}
+
+bool
+CmdHttpListener::isStopped() {
+ if (thread_pool_) {
+ return (thread_pool_->isStopped());
+ }
+
+ return (true);
+}
+
+bool
+CmdHttpListener::isPaused() {
+ if (thread_pool_) {
+ return (thread_pool_->isPaused());
+ }
+
+ return (false);
+}
+
+} // namespace config
+} // namespace isc
diff --git a/src/lib/config/cmd_http_listener.h b/src/lib/config/cmd_http_listener.h
new file mode 100644
index 0000000..4a87210
--- /dev/null
+++ b/src/lib/config/cmd_http_listener.h
@@ -0,0 +1,160 @@
+// 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 CMD_HTTP_LISTENER_H
+#define CMD_HTTP_LISTENER_H
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_service_thread_pool.h>
+#include <http/listener.h>
+#include <thread>
+#include <vector>
+
+namespace isc {
+namespace config {
+
+/// @brief A multi-threaded HTTP listener that can process API commands
+/// requests.
+///
+/// This class will listen for Command API client requests on a given
+/// IP address and port. It uses its own IOService instance to drive
+/// a thread-pool which can service multiple connections concurrently.
+/// The number of concurrent connections is currently limited to the
+/// configured thread pool size.
+///
+/// @note This class is NOT compatible with Kea core single-threading.
+/// It is incumbent upon the owner to ensure the Kea core multi-threading
+/// is (or will be) enabled when creating instances of this class.
+class CmdHttpListener {
+public:
+ /// @brief Constructor
+ CmdHttpListener(const asiolink::IOAddress& address, const uint16_t port,
+ const uint16_t thread_pool_size = 1,
+ asiolink::TlsContextPtr context = asiolink::TlsContextPtr());
+
+ /// @brief Destructor
+ virtual ~CmdHttpListener();
+
+ /// @brief Check if the current thread can perform thread pool state
+ /// transition.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions();
+
+ /// @brief Starts running the listener's thread pool.
+ void start();
+
+ /// @brief Pauses the listener's thread pool.
+ ///
+ /// Suspends thread pool event processing.
+ void pause();
+
+ /// @brief Resumes running the listener's thread pool.
+ ///
+ /// Resumes thread pool event processing.
+ void resume();
+
+ /// @brief Stops the listener's thread pool.
+ void stop();
+
+ /// @brief Indicates if the thread pool is running.
+ ///
+ /// @return True if the thread pool exists and it is in the RUNNING state,
+ /// false otherwise.
+ bool isRunning();
+
+ /// @brief Indicates if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool does not exist or it is in the STOPPED
+ /// state, false otherwise.
+ bool isStopped();
+
+ /// @brief Indicates if the thread pool is paused.
+ ///
+ /// @return True if the thread pool exists and it is in the PAUSED state,
+ /// false otherwise.
+ bool isPaused();
+
+ /// @brief Fetches the IP address on which to listen.
+ ///
+ /// @return IOAddress containing the address on which to listen.
+ isc::asiolink::IOAddress getAddress() const {
+ return (address_);
+ }
+
+ /// @brief Fetches the port number on which to listen.
+ ///
+ /// @return uint16_t containing the port number on which to listen.
+ uint16_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return uint16_t containing the maximum size of the thread pool.
+ uint16_t getThreadPoolSize() const {
+ return (thread_pool_size_);
+ }
+
+ /// @brief Fetches the TLS context.
+ ///
+ /// @return TLS context.
+ asiolink::TlsContextPtr getTlsContext() const {
+ return (tls_context_);
+ }
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return uint16_t containing the number of running threads.
+ uint16_t getThreadCount() const {
+ if (!thread_pool_) {
+ return (0);
+ }
+
+ return (thread_pool_->getThreadCount());
+ }
+
+ /// @brief Fetches a pointer to the internal IOService used to
+ /// drive the thread-pool in multi-threaded mode.
+ ///
+ /// @return pointer to the IOService instance, or an empty pointer
+ /// in single-threaded mode.
+ asiolink::IOServicePtr getThreadIOService() const {
+ return (thread_io_service_);
+ }
+
+private:
+ /// @brief IP address on which to listen.
+ isc::asiolink::IOAddress address_;
+
+ /// @brief Port on which to listen.
+ uint16_t port_;
+
+ /// @brief IOService instance that drives our IO.
+ isc::asiolink::IOServicePtr thread_io_service_;
+
+ /// @brief The HttpListener instance
+ http::HttpListenerPtr http_listener_;
+
+ /// @brief The number of threads that will call IOService_context::run().
+ std::size_t thread_pool_size_;
+
+ /// @brief The pool of threads that do IO work.
+ asiolink::IoServiceThreadPoolPtr thread_pool_;
+
+ /// @brief The TLS context.
+ asiolink::TlsContextPtr tls_context_;
+};
+
+/// @brief Defines a shared pointer to CmdHttpListener.
+typedef boost::shared_ptr<CmdHttpListener> CmdHttpListenerPtr;
+
+} // namespace isc::config
+} // namespace isc
+
+#endif // CMD_HTTP_LISTENER_H
diff --git a/src/lib/config/cmd_response_creator.cc b/src/lib/config/cmd_response_creator.cc
new file mode 100644
index 0000000..c586d98
--- /dev/null
+++ b/src/lib/config/cmd_response_creator.cc
@@ -0,0 +1,176 @@
+// 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 <config/cmd_response_creator.h>
+#include <config/command_mgr.h>
+#include <config/config_log.h>
+#include <cc/command_interpreter.h>
+#include <http/post_request_json.h>
+#include <http/response_json.h>
+#include <boost/pointer_cast.hpp>
+#include <iostream>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::http;
+using namespace std;
+
+namespace isc {
+namespace config {
+
+HttpAuthConfigPtr CmdResponseCreator::http_auth_config_;
+
+unordered_set<string> CmdResponseCreator::command_accept_list_;
+
+HttpRequestPtr
+CmdResponseCreator::createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+}
+
+HttpResponsePtr
+CmdResponseCreator::
+createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ HttpResponsePtr response = createStockHttpResponseInternal(request, status_code);
+ response->finalize();
+ return (response);
+}
+
+HttpResponsePtr
+CmdResponseCreator::
+createStockHttpResponseInternal(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed OK).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // We only accept HTTP version 1.0 or 1.1. If other version number is found
+ // we fall back to HTTP/1.0.
+ if ((http_version < HttpVersion(1, 0)) || (HttpVersion(1, 1) < http_version)) {
+ http_version.major_ = 1;
+ http_version.minor_ = 0;
+ }
+ // This will generate the response holding JSON content.
+ HttpResponsePtr response(new HttpResponseJson(http_version, status_code));
+ return (response);
+}
+
+HttpResponsePtr
+CmdResponseCreator::createDynamicHttpResponse(HttpRequestPtr request) {
+ HttpResponseJsonPtr http_response;
+
+ // Check the basic HTTP authentication.
+ if (http_auth_config_) {
+ http_response = http_auth_config_->checkAuth(*this, request);
+ if (http_response) {
+ return (http_response);
+ }
+ }
+
+ // The request is always non-null, because this is verified by the
+ // createHttpResponse method. Let's try to convert it to the
+ // PostHttpRequestJson type as this is the type generated by the
+ // createNewHttpRequest. If the conversion result is null it means that
+ // the caller did not use createNewHttpRequest method to create this
+ // instance. This is considered an error in the server logic.
+ PostHttpRequestJsonPtr request_json = boost::dynamic_pointer_cast<
+ PostHttpRequestJson>(request);
+ if (!request_json) {
+ // Notify the client that we have a problem with our server.
+ return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
+ }
+
+ // We have already checked that the request is finalized so the call
+ // to getBodyAsJson must not trigger an exception.
+ ConstElementPtr command = request_json->getBodyAsJson();
+
+ // Filter the command.
+ http_response = filterCommand(request, command, command_accept_list_);
+ if (http_response) {
+ return (http_response);
+ }
+
+ // Process command doesn't generate exceptions but can possibly return
+ // null response, if the handler is not implemented properly. This is
+ // again an internal server issue.
+ ConstElementPtr response = config::CommandMgr::instance().processCommand(command);
+
+ if (!response) {
+ // Notify the client that we have a problem with our server.
+ return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
+ }
+
+ // Normal Responses coming from the Kea Control Agent must always be wrapped in
+ // a list as they may contain responses from multiple daemons.
+ // If we're emulating that for backward compatibility, then we need to wrap
+ // the answer in a list if it isn't in one already.
+ if (emulateAgentResponse() && (response->getType() != Element::list)) {
+ ElementPtr response_list = Element::createList();
+ response_list->add(boost::const_pointer_cast<Element>(response));
+ response = response_list;
+ }
+
+ // The response is OK, so let's create new HTTP response with the status OK.
+ http_response = boost::dynamic_pointer_cast<
+ HttpResponseJson>(createStockHttpResponseInternal(request, HttpStatusCode::OK));
+ http_response->setBodyAsJson(response);
+ http_response->finalize();
+
+ return (http_response);
+}
+
+HttpResponseJsonPtr
+CmdResponseCreator::filterCommand(const HttpRequestPtr& request,
+ const ConstElementPtr& body,
+ const unordered_set<string>& accept) {
+ HttpResponseJsonPtr response;
+ if (!body || accept.empty()) {
+ return (response);
+ }
+ if (body->getType() != Element::map) {
+ return (response);
+ }
+ ConstElementPtr elem = body->get(CONTROL_COMMAND);
+ if (!elem || (elem->getType() != Element::string)) {
+ return (response);
+ }
+ string command = elem->stringValue();
+ if (command.empty() || accept.count(command)) {
+ return (response);
+ }
+
+ // Reject the command.
+ LOG_DEBUG(command_logger, DBG_COMMAND,
+ COMMAND_HTTP_LISTENER_COMMAND_REJECTED)
+ .arg(command)
+ .arg(request->getRemote());
+ // From CtrlAgentResponseCreator::createStockHttpResponseInternal.
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ if ((http_version < HttpVersion(1, 0)) ||
+ (HttpVersion(1, 1) < http_version)) {
+ http_version.major_ = 1;
+ http_version.minor_ = 0;
+ }
+ HttpStatusCode status_code = HttpStatusCode::FORBIDDEN;
+ response.reset(new HttpResponseJson(http_version, status_code));
+ ElementPtr response_body = Element::createMap();
+ uint16_t result = HttpResponse::statusCodeToNumber(status_code);
+ response_body->set(CONTROL_RESULT,
+ Element::create(static_cast<long long>(result)));
+ const string& text = HttpResponse::statusCodeToString(status_code);
+ response_body->set(CONTROL_TEXT, Element::create(text));
+ response->setBodyAsJson(response_body);
+ response->finalize();
+ return (response);
+}
+
+} // end of namespace isc::config
+} // end of namespace isc
diff --git a/src/lib/config/cmd_response_creator.h b/src/lib/config/cmd_response_creator.h
new file mode 100644
index 0000000..2d76749
--- /dev/null
+++ b/src/lib/config/cmd_response_creator.h
@@ -0,0 +1,132 @@
+// 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 CMD__RESPONSE_CREATOR_H
+#define CMD__RESPONSE_CREATOR_H
+
+#include <http/response_creator.h>
+#include <http/basic_auth_config.h>
+#include <boost/shared_ptr.hpp>
+#include <unordered_set>
+
+namespace isc {
+namespace config {
+
+/// @brief Concrete implementation of the HTTP response creator used
+/// for processing API commands
+///
+/// See the documentation of the @ref isc::http::HttpResponseCreator for
+/// the basic information how HTTP response creators are utilized by
+/// the libkea-http library to generate HTTP responses.
+///
+/// This creator expects that received requests are encapsulated in the
+/// @ref isc::http::PostHttpRequestJson objects. The generated responses
+/// are encapsulated in the HttpResponseJson objects.
+///
+/// This class uses @ref CommandMgr singleton to process commands
+/// conveyed in the HTTP body. The JSON responses returned by the manager
+/// are placed in the body of the generated HTTP responses.
+class CmdResponseCreator : public http::HttpResponseCreator {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param emulate_agent_response if true, responses for normal
+ /// command outcomes are guaranteed to be wrapped in an Element::list.
+ /// This emulates how kea-ctrl-agent forms responses. Defaults to true.
+ CmdResponseCreator(bool emulate_agent_response = true)
+ : emulate_agent_response_(emulate_agent_response) {};
+
+ /// @brief Create a new request.
+ ///
+ /// This method creates a bare instance of the @ref
+ /// isc::http::PostHttpRequestJson.
+ ///
+ /// @return Pointer to the new instance of the @ref
+ /// isc::http::PostHttpRequestJson.
+ virtual http::HttpRequestPtr createNewHttpRequest() const;
+
+ /// @brief Creates stock HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @param status_code Status code of the response.
+ /// @return Pointer to an @ref isc::http::HttpResponseJson object
+ /// representing stock HTTP response.
+ virtual http::HttpResponsePtr
+ createStockHttpResponse(const http::HttpRequestPtr& request,
+ const http::HttpStatusCode& status_code) const;
+
+ /// @brief Indicates whether or not agent response emulation is enabled.
+ ///
+ /// @return true if emulation is enabled.
+ bool emulateAgentResponse() {
+ return (emulate_agent_response_);
+ }
+
+ /// @brief Filter commands.
+ ///
+ /// From RBAC code: if the access list is empty or the command
+ /// cannot be found just return.
+ ///
+ /// @param request The HTTP request (for the HTTP version).
+ /// @param body The request body.
+ /// @param accept The accept access list.
+ http::HttpResponseJsonPtr
+ filterCommand(const http::HttpRequestPtr& request,
+ const data::ConstElementPtr& body,
+ const std::unordered_set<std::string>& accept);
+
+ /// @brief The server current authentication configuration.
+ ///
+ /// Default to the empty HttpAuthConfigPtr.
+ ///
+ /// @note: This is currently not used, except in unit-tests. For the time being,
+ /// we postponed writing the corresponding code in the HA, so http_auth_config_
+ /// is left to its empty default value.
+ static http::HttpAuthConfigPtr http_auth_config_;
+
+ /// @brief The server command accept list.
+ ///
+ /// Default to the empty list which means to accept everything.
+ static std::unordered_set<std::string> command_accept_list_;
+
+private:
+
+ /// @brief Creates un-finalized stock HTTP response.
+ ///
+ /// The un-finalized response is the response that can't be sent over the
+ /// wire until @c finalize() is called, which commits the contents of the
+ /// message body.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @param status_code Status code of the response.
+ /// @return Pointer to an @ref isc::http::HttpResponseJson object
+ /// representing stock HTTP response.
+ http::HttpResponsePtr
+ createStockHttpResponseInternal(const http::HttpRequestPtr& request,
+ const http::HttpStatusCode& status_code) const;
+
+ /// @brief Creates implementation specific HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @return Pointer to an object representing HTTP response.
+ virtual http::HttpResponsePtr
+ createDynamicHttpResponse(http::HttpRequestPtr request);
+
+ /// @brief Determines whether or not responses are enclosed in an Element::list.
+ /// Currently kea-ctrl-agent wraps all responses in a list, as it may have
+ /// response from more than one server. If this is true, we'll ensure
+ /// responses (other than error responses) are in a list.
+ bool emulate_agent_response_;
+};
+
+/// @brief Pointer to the @ref CmdResponseCreator.
+typedef boost::shared_ptr<CmdResponseCreator> CmdResponseCreatorPtr;
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/cmd_response_creator_factory.h b/src/lib/config/cmd_response_creator_factory.h
new file mode 100644
index 0000000..f9c4126
--- /dev/null
+++ b/src/lib/config/cmd_response_creator_factory.h
@@ -0,0 +1,63 @@
+// Copyright (C) 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 CMD_RESPONSE_CREATOR_FACTORY_H
+#define CMD_RESPONSE_CREATOR_FACTORY_H
+
+#include <config/cmd_response_creator.h>
+#include <http/response_creator_factory.h>
+
+namespace isc {
+namespace config {
+
+/// @brief HTTP response creator factory for an API listener
+///
+/// @param emulate_agent_response if true results for normal command
+/// outcomes are wrapped in Element::list. This emulates responses
+/// generated by kea-ctrl-agent. The value is passed into the
+/// CmdResponseCreator when created. Defaults to true.
+///
+/// See the documentation of the @ref isc::http::HttpResponseCreatorFactory
+/// for the details how the response factory object is used by the
+/// @ref isc::http::HttpListener.
+///
+/// This class always returns the same instance of the
+/// @ref CmdResponseCreator which @ref isc::http::HttpListener and
+/// @ref isc::http::HttpConnection classes use to generate HTTP response
+/// messages which comply with the formats required by the Control Agent.
+class CmdResponseCreatorFactory : public http::HttpResponseCreatorFactory {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates sole instance of the @ref CmdResponseCreator object
+ /// returned by the @ref CmdResponseCreatorFactory::create.
+ ///
+ /// @param emulate_agent_response if true, responses for normal
+ /// command outcomes are guaranteed to be wrapped in an Element::list.
+ /// This emulates how kea-ctrl-agent forms responses. Defaults to true.
+ CmdResponseCreatorFactory(bool emulate_agent_response = true)
+ : sole_creator_(new CmdResponseCreator(emulate_agent_response)) {
+ }
+
+ /// @brief Returns an instance of the @ref CmdResponseCreator which
+ /// is used by HTTP server to generate responses to commands.
+ ///
+ /// @return Pointer to the @ref CmdResponseCreator object.
+ virtual http::HttpResponseCreatorPtr create() const {
+ return (sole_creator_);
+ }
+
+private:
+
+ /// @brief Instance of the @ref CmdResponseCreator returned.
+ http::HttpResponseCreatorPtr sole_creator_;
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/cmds_impl.h b/src/lib/config/cmds_impl.h
new file mode 100644
index 0000000..f90cee4
--- /dev/null
+++ b/src/lib/config/cmds_impl.h
@@ -0,0 +1,78 @@
+// Copyright (C) 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/.
+#ifndef CMDS_IMPL_H
+#define CMDS_IMPL_H
+
+#include <config.h>
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <hooks/hooks.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+namespace isc {
+namespace config {
+
+/// @brief Base class that command handler implementers may use for common tasks.
+class CmdsImpl {
+protected:
+ /// @brief Extracts the command name and arguments from a Callout handle
+ ///
+ /// @param handle Callout context handle expected to contain the JSON command
+ /// text
+ ///
+ /// @throw isc::BadValue if the text does not contain a properly formed command
+ void extractCommand(hooks::CalloutHandle& handle) {
+ try {
+ data::ConstElementPtr command;
+ handle.getArgument("command", command);
+ cmd_name_ = parseCommand(cmd_args_, command);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "JSON command text is invalid: " << ex.what());
+ }
+ }
+
+ /// @brief Set the callout argument "response" to indicate success
+ ///
+ /// @param handle Callout context handle in which to set the "response" argument
+ /// @param text string text to be used as the response description
+ void setSuccessResponse(hooks::CalloutHandle& handle, const std::string& text) {
+ data::ConstElementPtr response = createAnswer(CONTROL_RESULT_SUCCESS, text);
+ setResponse (handle, response);
+ }
+
+ /// @brief Set the callout argument "response" to indicate an error
+ ///
+ /// @param handle Callout context handle in which to set the "response" argument
+ /// @param text string text to be used as the response description
+ /// @param status numeric value to use as the response result, defaults to
+ /// CONTROL_RESULT_ERROR
+ void setErrorResponse(hooks::CalloutHandle& handle, const std::string& text,
+ int status=CONTROL_RESULT_ERROR) {
+ data::ConstElementPtr response = createAnswer(status, text);
+ setResponse (handle, response);
+ }
+
+ /// @brief Set the callout argument "response" to the given response
+ ///
+ /// @param handle Callout context handle in which to set the "response" argument
+ /// @param response ElementPtr to the result to use as the response
+ void setResponse(hooks::CalloutHandle& handle, data::ConstElementPtr& response) {
+ handle.setArgument ("response", response);
+ }
+
+ /// @brief Stores the command name extracted by a call to extractCommand
+ std::string cmd_name_;
+
+ /// @brief Stores the command arguments extracted by a call to extractCommand
+ data::ConstElementPtr cmd_args_;
+};
+
+}
+}
+
+#endif // CMDS_IMPL_H
diff --git a/src/lib/config/command-socket.dox b/src/lib/config/command-socket.dox
new file mode 100644
index 0000000..3b5150a
--- /dev/null
+++ b/src/lib/config/command-socket.dox
@@ -0,0 +1,188 @@
+// 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/.
+
+/**
+ @page ctrlSocket Control Channel
+
+@section ctrlSocketOverview Control Channel Overview
+
+In many cases it is useful to manage certain aspects of the DHCP servers
+while they are running. In Kea, this may be done via the Control Channel.
+Control Channel allows an external entity (e.g. a tool run by a sysadmin
+or a script) to issue commands to the server which can influence its
+behavior or retrieve information from it. Several notable examples
+envisioned are: reconfiguration, statistics retrieval and manipulation,
+and shutdown.
+
+Communication over Control Channel is conducted using JSON structures.
+As of Kea 0.9.2, the only supported communication channel is UNIX stream
+socket, but additional types may be added in the future.
+
+If configured, Kea will open a socket and will listen for any incoming
+connections. A process connecting to this socket is expected to send JSON
+commands structured as follows:
+
+@code
+{
+ "command": "foo",
+ "arguments": {
+ "param_foo": "value1",
+ "param_bar": "value2",
+ ...
+ }
+}
+@endcode
+
+- command - is the name of command to execute and is mandatory.
+- arguments - contain a single parameter or a map or parameters
+required to carry out the given command. The exact content and format is command specific.
+
+The server will process the incoming command and then send a response of the form:
+
+@code
+{
+ "result": 0|1,
+ "text": "textual description",
+ "arguments": {
+ "argument1": "value1",
+ "argument2": "value2",
+ ...
+ }
+}
+@endcode
+
+- result - indicates the outcome of the command. A value of 0 means a success while
+any non-zero value designates an error. Currently 1 is used as a generic error, but additional
+error codes may be added in the future.
+- text field - typically appears when result is non-zero and contains description of the error
+encountered.
+- arguments - is a map of additional data values returned by the server, specific to the
+command issue. The map is always present, even if it contains no data values.
+
+@section ctrlSocketClient Using Control Channel
+
+Here are two examples of how to access the Control Channel:
+
+1. Use socat tool, which is available in many Linux and BSD distributions.
+See http://www.dest-unreach.org/socat/ for details. To use it:
+@code
+socat UNIX:/var/run/kea/kea4.sock -
+@endcode
+You then can type JSON commands and get responses (also in JSON format).
+
+2. Here's an example C code that connects and gets a list of supported commands:
+@code
+// 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 <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, const char* argv[]) {
+
+ if (argc != 2) {
+ printf("Usage: %s socket_path\n", argv[0]);
+ return (1);
+ }
+
+ // Create UNIX stream socket.
+ int socket_fd;
+ if ((socket_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ {
+ perror("Failed to create UNIX stream");
+ return (1);
+ }
+
+ // Specify the address to connect to (unix path)
+ struct sockaddr_un srv_addr;
+ memset(&srv_addr, 0, sizeof(struct sockaddr_un));
+ srv_addr.sun_family = AF_UNIX;
+ strcpy(srv_addr.sun_path, argv[1]);
+ socklen_t len = sizeof(srv_addr);
+
+ // Try to connect.
+ if (connect(socket_fd, (struct sockaddr*) &srv_addr, len) == -1) {
+ perror("Failed to connect");
+ return (1);
+ }
+
+ // Send a command to list all available commands.
+ char buf[1024];
+ sprintf(buf, "{ \"command\": \"list-commands\" }");
+ int bytes_sent = send(socket_fd, buf, strlen(buf), 0);
+ printf("%d bytes sent\n", bytes_sent);
+
+ // Receive a response (should be JSON formatted list of commands)
+ int bytes_rcvd = recv(socket_fd, buf, sizeof(buf), 0);
+ printf("%d bytes received: [%s]\n", bytes_rcvd, buf);
+
+ // Close the socket
+ close(socket_fd);
+
+ return 0;
+}
+@endcode
+
+@section ctrlSocketImpl Control Channel Implementation
+
+Control Channel is implemented in @ref isc::config::CommandMgr. It is a singleton
+class that allows registration of callbacks that handle specific commands.
+It internally supports a single command: @c list-commands that returns a list
+of supported commands. This component is expected to be shared among all daemons.
+
+There are 3 main methods that are expected to be used by developers:
+- @ref isc::config::CommandMgr::registerCommand, which allows registration of
+ additional commands.
+- @ref isc::config::CommandMgr::deregisterCommand, which allows removing previously
+ registered command.
+- @ref isc::config::CommandMgr::processCommand, which allows handling specified
+ command.
+
+There are also two methods for managing control sockets. They are not expected
+to be used directly, unless someone implements a new Control Channel (e.g. TCP
+or HTTPS connection):
+
+- @ref isc::config::CommandMgr::openCommandSocket that passes structure defined
+ in the configuration file. Currently only two parameters are supported: socket-type
+ (which must contain value 'unix') and socket-name (which contains unix path for
+ the named socket to be created).
+- @ref isc::config::CommandMgr::closeCommandSocket() - it is used to close the
+ socket.
+
+Kea servers use @c CommandMgr to register handlers for various commands they
+support natively. However, it is possible extend a set of supported commands
+using hooks framework. See @ref hooksdgCommandHandlers how to implement support
+for your own control commands in Kea.
+
+@section ctrlSocketConnections Accepting connections
+
+The @ref isc::config::CommandMgr is implemented using boost ASIO and uses
+asynchronous calls to accept new connections and receive commands from the
+controlling clients. ASIO uses IO service object to run asynchronous calls.
+Thus, before the server can use the @ref isc::config::CommandMgr it must
+provide it with a common instance of the @ref isc::asiolink::IOService
+object using @ref isc::config::CommandMgr::setIOService. The server's
+main loop must contain calls to @ref isc::asiolink::IOService::run or
+@ref isc::asiolink::IOService::poll or their variants to invoke Command
+Manager's handlers as required for processing control requests.
+
+@section ctrlSocketMTConsiderations Multi-Threading Consideration for Control Channel
+
+The control channel utilities are not thread safe but they are used only
+by the main thread so in most cases it does not matter. For instance
+the assumption that only at most one command can be executed at a given
+time can be done. Of course this has its limit: when the command changes
+the configuration or is incompatible with a simultaneous packet
+processing the multi-threading mode must be checked and service threads
+stopped.
+
+*/
diff --git a/src/lib/config/command_mgr.cc b/src/lib/config/command_mgr.cc
new file mode 100644
index 0000000..831d56e
--- /dev/null
+++ b/src/lib/config/command_mgr.cc
@@ -0,0 +1,667 @@
+// 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <asiolink/unix_domain_socket.h>
+#include <asiolink/unix_domain_socket_acceptor.h>
+#include <asiolink/unix_domain_socket_endpoint.h>
+#include <config/command_mgr.h>
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <cc/json_feed.h>
+#include <dhcp/iface_mgr.h>
+#include <config/config_log.h>
+#include <config/timeouts.h>
+#include <util/watch_socket.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <array>
+#include <functional>
+#include <unistd.h>
+#include <sys/file.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Maximum size of the data chunk sent/received over the socket.
+const size_t BUF_SIZE = 32768;
+
+class ConnectionPool;
+
+/// @brief Represents a single connection over control socket.
+///
+/// An instance of this object is created when the @c CommandMgr acceptor
+/// receives new connection from a controlling client.
+class Connection : public boost::enable_shared_from_this<Connection> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor registers a socket of this connection in the Interface
+ /// Manager to cause the blocking call to @c select() to return as soon as
+ /// a transmission over the control socket is received.
+ ///
+ /// It installs two external sockets on the @IfaceMgr to break synchronous
+ /// calls to @select(). The @c WatchSocket is used for send operations
+ /// over the connection. The native socket is used for signaling reads
+ /// over the connection.
+ ///
+ /// @param io_service IOService object used to handle the asio operations
+ /// @param socket Pointer to the object representing a socket which is used
+ /// for data transmission.
+ /// @param connection_pool Reference to the connection pool to which this
+ /// connection belongs.
+ /// @param timeout Connection timeout (in seconds).
+ Connection(const IOServicePtr& io_service,
+ const boost::shared_ptr<UnixDomainSocket>& socket,
+ ConnectionPool& connection_pool,
+ const long timeout)
+ : socket_(socket), timeout_timer_(*io_service), timeout_(timeout),
+ buf_(), response_(), connection_pool_(connection_pool), feed_(),
+ response_in_progress_(false), watch_socket_(new util::WatchSocket()) {
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_OPENED)
+ .arg(socket_->getNative());
+
+ // Callback value of 0 is used to indicate that callback function is
+ // not installed.
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(watch_socket_->getSelectFd(), 0);
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(socket_->getNative(), 0);
+
+ // Initialize state model for receiving and preparsing commands.
+ feed_.initModel();
+
+ // Start timer for detecting timeouts.
+ scheduleTimer();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Cancels timeout timer if one is scheduled.
+ ~Connection() {
+ timeout_timer_.cancel();
+ }
+
+ /// @brief This method schedules timer or reschedules existing timer.
+ void scheduleTimer() {
+ timeout_timer_.setup(std::bind(&Connection::timeoutHandler, this),
+ timeout_, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Close current connection.
+ ///
+ /// Connection is not closed if the invocation of this method is a result of
+ /// server reconfiguration. The connection will be closed once a response is
+ /// sent to the client. Closing a socket during processing a request would
+ /// cause the server to not send a response to the client.
+ void stop() {
+ if (!response_in_progress_) {
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_CLOSED)
+ .arg(socket_->getNative());
+
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(watch_socket_->getSelectFd());
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_->getNative());
+
+ // Close watch socket and log errors if occur.
+ std::string watch_error;
+ if (!watch_socket_->closeSocket(watch_error)) {
+ LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLOSE_ERROR)
+ .arg(watch_error);
+ }
+
+ socket_->close();
+ timeout_timer_.cancel();
+ }
+ }
+
+ /// @brief Gracefully terminates current connection.
+ ///
+ /// This method should be called prior to closing the socket to initiate
+ /// graceful shutdown.
+ void terminate();
+
+ /// @brief Start asynchronous read over the unix domain socket.
+ ///
+ /// This method doesn't block. Once the transmission is received over the
+ /// socket, the @c Connection::receiveHandler callback is invoked to
+ /// process received data.
+ void doReceive() {
+ socket_->asyncReceive(&buf_[0], sizeof(buf_),
+ std::bind(&Connection::receiveHandler,
+ shared_from_this(), ph::_1, ph::_2));
+ }
+
+ /// @brief Starts asynchronous send over the unix domain socket.
+ ///
+ /// This method doesn't block. Once the send operation (that covers the whole
+ /// data if it's small or first BUF_SIZE bytes if its large) is completed, the
+ /// @c Connection::sendHandler callback is invoked. That handler will either
+ /// close the connection gracefully if all data has been sent, or will
+ /// call @ref doSend() again to send the next chunk of data.
+ void doSend() {
+ size_t chunk_size = (response_.size() < BUF_SIZE) ? response_.size() : BUF_SIZE;
+ socket_->asyncSend(&response_[0], chunk_size,
+ std::bind(&Connection::sendHandler, shared_from_this(), ph::_1, ph::_2));
+
+ // Asynchronous send has been scheduled and we need to indicate this
+ // to break the synchronous select(). The handler should clear this
+ // status when invoked.
+ try {
+ watch_socket_->markReady();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_MARK_READY_ERROR)
+ .arg(ex.what());
+ }
+ }
+
+ /// @brief Handler invoked when the data is received over the control
+ /// socket.
+ ///
+ /// It collects received data into the @c isc::config::JSONFeed object and
+ /// schedules additional asynchronous read of data if this object signals
+ /// that command is incomplete. When the entire command is received, the
+ /// handler processes this command and asynchronously responds to the
+ /// controlling client.
+ //
+ ///
+ /// @param ec Error code.
+ /// @param bytes_transferred Number of bytes received.
+ void receiveHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred);
+
+
+ /// @brief Handler invoked when the data is sent over the control socket.
+ ///
+ /// If there are still data to be sent, another asynchronous send is
+ /// scheduled. When the entire command is sent, the connection is shutdown
+ /// and closed.
+ ///
+ /// @param ec Error code.
+ /// @param bytes_transferred Number of bytes sent.
+ void sendHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred);
+
+ /// @brief Handler invoked when timeout has occurred.
+ ///
+ /// Asynchronously sends a response to the client indicating that the
+ /// timeout has occurred.
+ void timeoutHandler();
+
+private:
+
+ /// @brief Pointer to the socket used for transmission.
+ boost::shared_ptr<UnixDomainSocket> socket_;
+
+ /// @brief Interval timer used to detect connection timeouts.
+ IntervalTimer timeout_timer_;
+
+ /// @brief Connection timeout (in milliseconds)
+ long timeout_;
+
+ /// @brief Buffer used for received data.
+ std::array<char, BUF_SIZE> buf_;
+
+ /// @brief Response created by the server.
+ std::string response_;
+
+ /// @brief Reference to the pool of connections.
+ ConnectionPool& connection_pool_;
+
+ /// @brief State model used to receive data over the connection and detect
+ /// when the command ends.
+ JSONFeed feed_;
+
+ /// @brief Boolean flag indicating if the request to stop connection is a
+ /// result of server reconfiguration.
+ bool response_in_progress_;
+
+ /// @brief Pointer to watch socket instance used to signal that the socket
+ /// is ready for read or write.
+ util::WatchSocketPtr watch_socket_;
+};
+
+/// @brief Pointer to the @c Connection.
+typedef boost::shared_ptr<Connection> ConnectionPtr;
+
+/// @brief Holds all open connections.
+class ConnectionPool {
+public:
+
+ /// @brief Starts new connection.
+ ///
+ /// @param connection Pointer to the new connection object.
+ void start(const ConnectionPtr& connection) {
+ connection->doReceive();
+ connections_.insert(connection);
+ }
+
+ /// @brief Stops running connection.
+ ///
+ /// @param connection Pointer to the new connection object.
+ void stop(const ConnectionPtr& connection) {
+ try {
+ connection->stop();
+ connections_.erase(connection);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CLOSE_FAIL)
+ .arg(ex.what());
+ }
+ }
+
+ /// @brief Stops all connections which are allowed to stop.
+ void stopAll() {
+ for (auto conn = connections_.begin(); conn != connections_.end();
+ ++conn) {
+ (*conn)->stop();
+ }
+ connections_.clear();
+ }
+
+private:
+
+ /// @brief Pool of connections.
+ std::set<ConnectionPtr> connections_;
+
+};
+
+void
+Connection::terminate() {
+ try {
+ socket_->shutdown();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL)
+ .arg(ex.what());
+ }
+}
+
+void
+Connection::receiveHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ if (ec) {
+ if (ec.value() == boost::asio::error::eof) {
+ std::stringstream os;
+ if (feed_.getProcessedText().empty()) {
+ os << "no input data to discard";
+ } else {
+ os << "discarding partial command of "
+ << feed_.getProcessedText().size() << " bytes";
+ }
+
+ // Foreign host has closed the connection. We should remove it from the
+ // connection pool.
+ LOG_INFO(command_logger, COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST)
+ .arg(socket_->getNative()).arg(os.str());
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_READ_FAIL)
+ .arg(ec.value()).arg(socket_->getNative());
+ }
+
+ connection_pool_.stop(shared_from_this());
+ return;
+
+ } else if (bytes_transferred == 0) {
+ // Nothing received. Close the connection.
+ connection_pool_.stop(shared_from_this());
+ return;
+ }
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ)
+ .arg(bytes_transferred).arg(socket_->getNative());
+
+ // Reschedule the timer because the transaction is ongoing.
+ scheduleTimer();
+
+ ConstElementPtr cmd;
+ ConstElementPtr rsp;
+
+ try {
+ // Received some data over the socket. Append them to the JSON feed
+ // to see if we have reached the end of command.
+ feed_.postBuffer(&buf_[0], bytes_transferred);
+ feed_.poll();
+ // If we haven't yet received the full command, continue receiving.
+ if (feed_.needData()) {
+ doReceive();
+ return;
+ }
+
+ // Received entire command. Parse the command into JSON.
+ if (feed_.feedOk()) {
+ cmd = feed_.toElement();
+ response_in_progress_ = true;
+
+ // Cancel the timer to make sure that long lasting command
+ // processing doesn't cause the timeout.
+ timeout_timer_.cancel();
+
+ // If successful, then process it as a command.
+ rsp = CommandMgr::instance().processCommand(cmd);
+
+ response_in_progress_ = false;
+
+ } else {
+ // Failed to parse command as JSON or process the received command.
+ // This exception will be caught below and the error response will
+ // be sent.
+ isc_throw(BadValue, feed_.getErrorMessage());
+ }
+
+ } catch (const Exception& ex) {
+ LOG_WARN(command_logger, COMMAND_PROCESS_ERROR1).arg(ex.what());
+ rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
+ }
+
+ // No response generated. Connection will be closed.
+ if (!rsp) {
+ LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR)
+ .arg(cmd ? cmd->str() : "unknown");
+ rsp = createAnswer(CONTROL_RESULT_ERROR,
+ "internal server error: no response generated");
+
+ } else {
+
+ // Reschedule the timer as it may be either canceled or need to be
+ // updated to not timeout before we manage to the send the reply.
+ scheduleTimer();
+
+ // Let's convert JSON response to text. Note that at this stage
+ // the rsp pointer is always set.
+ response_ = rsp->str();
+
+ doSend();
+ return;
+ }
+
+ // Close the connection if we have sent the entire response.
+ connection_pool_.stop(shared_from_this());
+}
+
+void
+Connection::sendHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ // Clear the watch socket so as the future send operation can mark it
+ // again to interrupt the synchronous select() call.
+ try {
+ watch_socket_->clearReady();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLEAR_ERROR)
+ .arg(ex.what());
+ }
+
+ if (ec) {
+ // If an error occurred, log this error and stop the connection.
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL)
+ .arg(socket_->getNative()).arg(ec.message());
+ }
+
+ } else {
+
+ // Reschedule the timer because the transaction is ongoing.
+ scheduleTimer();
+
+ // No error. We are in a process of sending a response. Need to
+ // remove the chunk that we have managed to sent with the previous
+ // attempt.
+ response_.erase(0, bytes_transferred);
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE)
+ .arg(bytes_transferred).arg(response_.size())
+ .arg(socket_->getNative());
+
+ // Check if there is any data left to be sent and sent it.
+ if (!response_.empty()) {
+ doSend();
+ return;
+ }
+
+ // Gracefully shutdown the connection and close the socket if
+ // we have sent the whole response.
+ terminate();
+ }
+
+ // All data sent or an error has occurred. Close the connection.
+ connection_pool_.stop(shared_from_this());
+}
+
+void
+Connection::timeoutHandler() {
+ LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_TIMEOUT)
+ .arg(socket_->getNative());
+
+ try {
+ socket_->cancel();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CANCEL_FAIL)
+ .arg(socket_->getNative())
+ .arg(ex.what());
+ }
+
+ std::stringstream os;
+ os << "Connection over control channel timed out";
+ if (!feed_.getProcessedText().empty()) {
+ os << ", discarded partial command of "
+ << feed_.getProcessedText().size() << " bytes";
+ }
+
+ ConstElementPtr rsp = createAnswer(CONTROL_RESULT_ERROR, os.str());
+ response_ = rsp->str();
+ doSend();
+}
+
+
+}
+
+namespace isc {
+namespace config {
+
+/// @brief Implementation of the @c CommandMgr.
+class CommandMgrImpl {
+public:
+
+ /// @brief Constructor.
+ CommandMgrImpl()
+ : io_service_(), acceptor_(), socket_(), socket_name_(),
+ connection_pool_(), timeout_(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND) {
+ }
+
+ /// @brief Opens acceptor service allowing the control clients to connect.
+ ///
+ /// @param socket_info Configuration information for the control socket.
+ /// @throw BadSocketInfo When socket configuration is invalid.
+ /// @throw SocketError When socket operation fails.
+ void openCommandSocket(const isc::data::ConstElementPtr& socket_info);
+
+ /// @brief Asynchronously accepts next connection.
+ void doAccept();
+
+ /// @brief Returns the lock file name
+ std::string getLockName() {
+ return (std::string(socket_name_ + ".lock"));
+ }
+
+ /// @brief Pointer to the IO service used by the server process for running
+ /// asynchronous tasks.
+ IOServicePtr io_service_;
+
+ /// @brief Pointer to the acceptor service.
+ boost::shared_ptr<UnixDomainSocketAcceptor> acceptor_;
+
+ /// @brief Pointer to the socket into which the new connection is accepted.
+ boost::shared_ptr<UnixDomainSocket> socket_;
+
+ /// @brief Path to the unix domain socket descriptor.
+ ///
+ /// This is used to remove the socket file once the connection terminates.
+ std::string socket_name_;
+
+ /// @brief Pool of connections.
+ ConnectionPool connection_pool_;
+
+ /// @brief Connection timeout
+ long timeout_;
+};
+
+void
+CommandMgrImpl::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
+ socket_name_.clear();
+
+ if(!socket_info) {
+ isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
+ }
+
+ ConstElementPtr type = socket_info->get("socket-type");
+ if (!type) {
+ isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
+ }
+
+ // Only supporting unix sockets right now.
+ if (type->stringValue() != "unix") {
+ isc_throw(BadSocketInfo, "Invalid 'socket-type' parameter value "
+ << type->stringValue());
+ }
+
+ // UNIX socket is requested. It takes one parameter: socket-name that
+ // specifies UNIX path of the socket.
+ ConstElementPtr name = socket_info->get("socket-name");
+ if (!name) {
+ isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
+ }
+
+ if (name->getType() != Element::string) {
+ isc_throw(BadSocketInfo, "'socket-name' parameter expected to be a string");
+ }
+
+ socket_name_ = name->stringValue();
+
+ // First let's open lock file.
+ std::string lock_name = getLockName();
+ int lock_fd = open(lock_name.c_str(), O_RDONLY | O_CREAT, 0600);
+ if (lock_fd == -1) {
+ std::string errmsg = strerror(errno);
+ isc_throw(SocketError, "cannot create socket lockfile, "
+ << lock_name << ", : " << errmsg);
+ }
+
+ // Try to acquire lock. If we can't somebody else is actively
+ // using it.
+ int ret = flock(lock_fd, LOCK_EX | LOCK_NB);
+ if (ret != 0) {
+ std::string errmsg = strerror(errno);
+ isc_throw(SocketError, "cannot lock socket lockfile, "
+ << lock_name << ", : " << errmsg);
+ }
+
+ // We have the lock, so let's remove the pre-existing socket
+ // file if it exists.
+ static_cast<void>(::remove(socket_name_.c_str()));
+
+ LOG_INFO(command_logger, COMMAND_ACCEPTOR_START)
+ .arg(socket_name_);
+
+ try {
+ // Start asynchronous acceptor service.
+ acceptor_.reset(new UnixDomainSocketAcceptor(*io_service_));
+ UnixDomainSocketEndpoint endpoint(socket_name_);
+ acceptor_->open(endpoint);
+ acceptor_->bind(endpoint);
+ acceptor_->listen();
+ // Install this socket in Interface Manager.
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(acceptor_->getNative(), 0);
+
+ doAccept();
+
+ } catch (const std::exception& ex) {
+ isc_throw(SocketError, ex.what());
+ }
+}
+
+void
+CommandMgrImpl::doAccept() {
+ // Create a socket into which the acceptor will accept new connection.
+ socket_.reset(new UnixDomainSocket(*io_service_));
+ acceptor_->asyncAccept(*socket_, [this](const boost::system::error_code& ec) {
+ if (!ec) {
+ // New connection is arriving. Start asynchronous transmission.
+ ConnectionPtr connection(new Connection(io_service_, socket_,
+ connection_pool_,
+ timeout_));
+ connection_pool_.start(connection);
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_ACCEPT_FAIL)
+ .arg(acceptor_->getNative()).arg(ec.message());
+ }
+
+ // Unless we're stopping the service, start accepting connections again.
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ doAccept();
+ }
+ });
+}
+
+CommandMgr::CommandMgr()
+ : HookedCommandMgr(), impl_(new CommandMgrImpl()) {
+}
+
+void
+CommandMgr::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
+ impl_->openCommandSocket(socket_info);
+}
+
+void CommandMgr::closeCommandSocket() {
+ // Close acceptor if the acceptor is open.
+ if (impl_->acceptor_ && impl_->acceptor_->isOpen()) {
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(impl_->acceptor_->getNative());
+ impl_->acceptor_->close();
+ static_cast<void>(::remove(impl_->socket_name_.c_str()));
+ static_cast<void>(::remove(impl_->getLockName().c_str()));
+ }
+
+ // Stop all connections which can be closed. The only connection that won't
+ // be closed is the one over which we have received a request to reconfigure
+ // the server. This connection will be held until the CommandMgr responds to
+ // such request.
+ impl_->connection_pool_.stopAll();
+}
+
+int
+CommandMgr::getControlSocketFD() {
+ return (impl_->acceptor_ ? impl_->acceptor_->getNative() : -1);
+}
+
+
+CommandMgr&
+CommandMgr::instance() {
+ static CommandMgr cmd_mgr;
+ return (cmd_mgr);
+}
+
+void
+CommandMgr::setIOService(const IOServicePtr& io_service) {
+ impl_->io_service_ = io_service;
+}
+
+void
+CommandMgr::setConnectionTimeout(const long timeout) {
+ impl_->timeout_ = timeout;
+}
+
+
+}; // end of isc::config
+}; // end of isc
diff --git a/src/lib/config/command_mgr.h b/src/lib/config/command_mgr.h
new file mode 100644
index 0000000..f4ba6c1
--- /dev/null
+++ b/src/lib/config/command_mgr.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2015-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 COMMAND_MGR_H
+#define COMMAND_MGR_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <config/hooked_command_mgr.h>
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace config {
+
+/// @brief An exception indicating that specified socket parameters are invalid
+class BadSocketInfo : public Exception {
+public:
+ BadSocketInfo(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief An exception indicating a problem with socket operation
+class SocketError : public Exception {
+public:
+ SocketError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+class CommandMgrImpl;
+
+/// @brief Commands Manager implementation for the Kea servers.
+///
+/// This class extends @ref BaseCommandMgr with the ability to receive and
+/// respond to commands over unix domain sockets.
+class CommandMgr : public HookedCommandMgr, public boost::noncopyable {
+public:
+
+ /// @brief CommandMgr is a singleton class. This method returns reference
+ /// to its sole instance.
+ ///
+ /// @return the only existing instance of the manager
+ static CommandMgr& instance();
+
+ /// @brief Sets IO service to be used by the command manager.
+ ///
+ /// The server should use this method to provide the Command Manager with the
+ /// common IO service used by the server.
+ /// @param io_service Pointer to the IO service.
+ void setIOService(const asiolink::IOServicePtr& io_service);
+
+ /// @brief Override default connection timeout.
+ ///
+ /// @param timeout New connection timeout in milliseconds.
+ void setConnectionTimeout(const long timeout);
+
+ /// @brief Opens control socket with parameters specified in socket_info
+ ///
+ /// Currently supported types are:
+ /// - unix (required parameters: socket-type: unix, socket-name:/unix/path)
+ ///
+ /// @throw BadSocketInfo When socket configuration is invalid.
+ /// @throw SocketError When socket operation fails.
+ ///
+ /// @param socket_info Configuration information for the control socket.
+ void
+ openCommandSocket(const isc::data::ConstElementPtr& socket_info);
+
+ /// @brief Shuts down any open control sockets
+ void closeCommandSocket();
+
+ /// @brief Returns control socket descriptor
+ ///
+ /// This method should be used only in tests.
+ int getControlSocketFD();
+
+private:
+
+ /// @brief Private constructor
+ CommandMgr();
+
+ /// @brief Pointer to the implementation of the @ref CommandMgr.
+ boost::shared_ptr<CommandMgrImpl> impl_;
+};
+
+}; // end of isc::config namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/config/config_log.cc b/src/lib/config/config_log.cc
new file mode 100644
index 0000000..67f896f
--- /dev/null
+++ b/src/lib/config/config_log.cc
@@ -0,0 +1,22 @@
+// 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/.
+
+/// Defines the logger used by the config lib
+
+#include <config.h>
+
+#include "config/config_log.h"
+
+namespace isc {
+namespace config {
+
+isc::log::Logger command_logger("commands");
+
+extern const int DBG_COMMAND = isc::log::DBGLVL_COMMAND;
+
+} // namespace nsas
+} // namespace isc
+
diff --git a/src/lib/config/config_log.h b/src/lib/config/config_log.h
new file mode 100644
index 0000000..08b4ddb
--- /dev/null
+++ b/src/lib/config/config_log.h
@@ -0,0 +1,29 @@
+// 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 CONFIG_LOG_H
+#define CONFIG_LOG_H
+
+#include <log/macros.h>
+#include "config_messages.h"
+
+namespace isc {
+namespace config {
+
+/// @brief Command processing Logger
+///
+/// Define the logger used to log messages related to command processing.
+/// We could define it in multiple modules, but defining in a single
+/// module and linking to it saves time and space.
+extern isc::log::Logger command_logger;
+
+// Enumerate configuration elements as they are processed.
+extern const int DBG_COMMAND;
+
+} // namespace config
+} // namespace isc
+
+#endif // CONFIG_LOG_H
diff --git a/src/lib/config/config_messages.cc b/src/lib/config/config_messages.cc
new file mode 100644
index 0000000..f2754a1
--- /dev/null
+++ b/src/lib/config/config_messages.cc
@@ -0,0 +1,77 @@
+// File created from ../../../src/lib/config/config_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace config {
+
+extern const isc::log::MessageID COMMAND_ACCEPTOR_START = "COMMAND_ACCEPTOR_START";
+extern const isc::log::MessageID COMMAND_DEREGISTERED = "COMMAND_DEREGISTERED";
+extern const isc::log::MessageID COMMAND_EXTENDED_REGISTERED = "COMMAND_EXTENDED_REGISTERED";
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_COMMAND_REJECTED = "COMMAND_HTTP_LISTENER_COMMAND_REJECTED";
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STARTED = "COMMAND_HTTP_LISTENER_STARTED";
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPED = "COMMAND_HTTP_LISTENER_STOPPED";
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPING = "COMMAND_HTTP_LISTENER_STOPPING";
+extern const isc::log::MessageID COMMAND_PROCESS_ERROR1 = "COMMAND_PROCESS_ERROR1";
+extern const isc::log::MessageID COMMAND_PROCESS_ERROR2 = "COMMAND_PROCESS_ERROR2";
+extern const isc::log::MessageID COMMAND_RECEIVED = "COMMAND_RECEIVED";
+extern const isc::log::MessageID COMMAND_REGISTERED = "COMMAND_REGISTERED";
+extern const isc::log::MessageID COMMAND_RESPONSE_ERROR = "COMMAND_RESPONSE_ERROR";
+extern const isc::log::MessageID COMMAND_SOCKET_ACCEPT_FAIL = "COMMAND_SOCKET_ACCEPT_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST = "COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CANCEL_FAIL = "COMMAND_SOCKET_CONNECTION_CANCEL_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSED = "COMMAND_SOCKET_CONNECTION_CLOSED";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSE_FAIL = "COMMAND_SOCKET_CONNECTION_CLOSE_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_OPENED = "COMMAND_SOCKET_CONNECTION_OPENED";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL = "COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_TIMEOUT = "COMMAND_SOCKET_CONNECTION_TIMEOUT";
+extern const isc::log::MessageID COMMAND_SOCKET_READ = "COMMAND_SOCKET_READ";
+extern const isc::log::MessageID COMMAND_SOCKET_READ_FAIL = "COMMAND_SOCKET_READ_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_WRITE = "COMMAND_SOCKET_WRITE";
+extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL = "COMMAND_SOCKET_WRITE_FAIL";
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR = "COMMAND_WATCH_SOCKET_CLEAR_ERROR";
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR = "COMMAND_WATCH_SOCKET_CLOSE_ERROR";
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR = "COMMAND_WATCH_SOCKET_MARK_READY_ERROR";
+
+} // namespace config
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "COMMAND_ACCEPTOR_START", "Starting to accept connections via unix domain socket bound to %1",
+ "COMMAND_DEREGISTERED", "Command %1 deregistered",
+ "COMMAND_EXTENDED_REGISTERED", "Command %1 registered",
+ "COMMAND_HTTP_LISTENER_COMMAND_REJECTED", "Command HTTP listener rejected command '%1' from '%2'",
+ "COMMAND_HTTP_LISTENER_STARTED", "Command HTTP listener started with %1 threads, listening on %2:%3, use TLS: %4",
+ "COMMAND_HTTP_LISTENER_STOPPED", "Command HTTP listener for %1:%2 stopped.",
+ "COMMAND_HTTP_LISTENER_STOPPING", "Stopping Command HTTP listener for %1:%2",
+ "COMMAND_PROCESS_ERROR1", "Error while processing command: %1",
+ "COMMAND_PROCESS_ERROR2", "Error while processing command: %1",
+ "COMMAND_RECEIVED", "Received command '%1'",
+ "COMMAND_REGISTERED", "Command %1 registered",
+ "COMMAND_RESPONSE_ERROR", "Server failed to generate response for command: %1",
+ "COMMAND_SOCKET_ACCEPT_FAIL", "Failed to accept incoming connection on command socket %1: %2",
+ "COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST", "Closed command socket %1 by foreign host, %2",
+ "COMMAND_SOCKET_CONNECTION_CANCEL_FAIL", "Failed to cancel read operation on socket %1: %2",
+ "COMMAND_SOCKET_CONNECTION_CLOSED", "Closed socket %1 for existing command connection",
+ "COMMAND_SOCKET_CONNECTION_CLOSE_FAIL", "Failed to close command connection: %1",
+ "COMMAND_SOCKET_CONNECTION_OPENED", "Opened socket %1 for incoming command connection",
+ "COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL", "Encountered error %1 while trying to gracefully shutdown socket",
+ "COMMAND_SOCKET_CONNECTION_TIMEOUT", "Timeout occurred for connection over socket %1",
+ "COMMAND_SOCKET_READ", "Received %1 bytes over command socket %2",
+ "COMMAND_SOCKET_READ_FAIL", "Encountered error %1 while reading from command socket %2",
+ "COMMAND_SOCKET_WRITE", "Sent response of %1 bytes (%2 bytes left to send) over command socket %3",
+ "COMMAND_SOCKET_WRITE_FAIL", "Error while writing to command socket %1 : %2",
+ "COMMAND_WATCH_SOCKET_CLEAR_ERROR", "watch socket failed to clear: %1",
+ "COMMAND_WATCH_SOCKET_CLOSE_ERROR", "watch socket failed to close: %1",
+ "COMMAND_WATCH_SOCKET_MARK_READY_ERROR", "watch socket failed to mark ready: %1",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/config/config_messages.h b/src/lib/config/config_messages.h
new file mode 100644
index 0000000..3aac871
--- /dev/null
+++ b/src/lib/config/config_messages.h
@@ -0,0 +1,42 @@
+// File created from ../../../src/lib/config/config_messages.mes
+
+#ifndef CONFIG_MESSAGES_H
+#define CONFIG_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace config {
+
+extern const isc::log::MessageID COMMAND_ACCEPTOR_START;
+extern const isc::log::MessageID COMMAND_DEREGISTERED;
+extern const isc::log::MessageID COMMAND_EXTENDED_REGISTERED;
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_COMMAND_REJECTED;
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STARTED;
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPED;
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPING;
+extern const isc::log::MessageID COMMAND_PROCESS_ERROR1;
+extern const isc::log::MessageID COMMAND_PROCESS_ERROR2;
+extern const isc::log::MessageID COMMAND_RECEIVED;
+extern const isc::log::MessageID COMMAND_REGISTERED;
+extern const isc::log::MessageID COMMAND_RESPONSE_ERROR;
+extern const isc::log::MessageID COMMAND_SOCKET_ACCEPT_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CANCEL_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSED;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSE_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_OPENED;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_TIMEOUT;
+extern const isc::log::MessageID COMMAND_SOCKET_READ;
+extern const isc::log::MessageID COMMAND_SOCKET_READ_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_WRITE;
+extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL;
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR;
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR;
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR;
+
+} // namespace config
+} // namespace isc
+
+#endif // CONFIG_MESSAGES_H
diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes
new file mode 100644
index 0000000..18f764f
--- /dev/null
+++ b/src/lib/config/config_messages.mes
@@ -0,0 +1,143 @@
+# Copyright (C) 2011-2023 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$NAMESPACE isc::config
+
+% COMMAND_ACCEPTOR_START Starting to accept connections via unix domain socket bound to %1
+This informational message is issued when the Kea server starts an acceptor
+via which it is going to accept new control connections. The acceptor is
+bound to the endpoint associated with the filename provided as an argument.
+If starting the acceptor fails, subsequent error messages will provide a
+reason for failure.
+
+% COMMAND_DEREGISTERED Command %1 deregistered
+This debug message indicates that the daemon stopped supporting specified
+command. This command can no longer be issued. If the command socket is
+open and this command is issued, the daemon will not be able to process it.
+
+% COMMAND_EXTENDED_REGISTERED Command %1 registered
+This debug message indicates that the daemon started supporting specified
+command. The handler for the registered command includes a parameter holding
+entire command to be processed.
+
+% COMMAND_HTTP_LISTENER_COMMAND_REJECTED Command HTTP listener rejected command '%1' from '%2'
+This debug messages is issued when a command is rejected. Arguments detail
+the command and the address the request was received from.
+
+% COMMAND_HTTP_LISTENER_STARTED Command HTTP listener started with %1 threads, listening on %2:%3, use TLS: %4
+This debug messages is issued when an HTTP listener has been started to
+accept connections from Command API clients through which commands can be
+received and responses sent. Arguments detail the number of threads
+that the listener is using, the address and port at which it is listening,
+and if HTTPS/TLS is used or not.
+
+% COMMAND_HTTP_LISTENER_STOPPED Command HTTP listener for %1:%2 stopped.
+This debug messages is issued when the Command HTTP listener, listening
+at the given address and port, has completed shutdown.
+
+% COMMAND_HTTP_LISTENER_STOPPING Stopping Command HTTP listener for %1:%2
+This debug messages is issued when the Command HTTP listener, listening
+at the given address and port, has begun to shutdown.
+
+% COMMAND_PROCESS_ERROR1 Error while processing command: %1
+This warning message indicates that the server encountered an error while
+processing received command. Additional information will be provided, if
+available. Additional log messages may provide more details.
+
+% COMMAND_PROCESS_ERROR2 Error while processing command: %1
+This warning message indicates that the server encountered an error while
+processing received command. The difference, compared to COMMAND_PROCESS_ERROR1
+is that the initial command was well formed and the error occurred during
+logic processing, not the command parsing. Additional information will be
+provided, if available. Additional log messages may provide more details.
+
+% COMMAND_RECEIVED Received command '%1'
+This informational message indicates that a command was received over command
+socket. The nature of this command and its possible results will be logged
+with separate messages.
+
+% COMMAND_REGISTERED Command %1 registered
+This debug message indicates that the daemon started supporting specified
+command. If the command socket is open, this command can now be issued.
+
+% COMMAND_RESPONSE_ERROR Server failed to generate response for command: %1
+This error message indicates that the server failed to generate response for
+specified command. This likely indicates a server logic error, as the server
+is expected to generate valid responses for all commands, even malformed
+ones.
+
+% COMMAND_SOCKET_ACCEPT_FAIL Failed to accept incoming connection on command socket %1: %2
+This error indicates that the server detected incoming connection and executed
+accept system call on said socket, but this call returned an error. Additional
+information may be provided by the system as second parameter.
+
+% COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST Closed command socket %1 by foreign host, %2
+This is an information message indicating that the command connection has been
+closed by a command control client, and whether any partially read data
+was discarded.
+
+% COMMAND_SOCKET_CONNECTION_CANCEL_FAIL Failed to cancel read operation on socket %1: %2
+This error message is issued to indicate an error to cancel asynchronous read
+of the control command over the control socket. The cancel operation is performed
+when the timeout occurs during communication with a client. The error message
+includes details about the reason for failure.
+
+% COMMAND_SOCKET_CONNECTION_CLOSED Closed socket %1 for existing command connection
+This is a debug message indicating that the socket created for handling
+client's connection is closed. This usually means that the client disconnected,
+but may also mean a timeout.
+
+% COMMAND_SOCKET_CONNECTION_CLOSE_FAIL Failed to close command connection: %1
+This error message is issued when an error occurred when closing a
+command connection and/or removing it from the connections pool. The
+detailed error is provided as an argument.
+
+% COMMAND_SOCKET_CONNECTION_OPENED Opened socket %1 for incoming command connection
+This is a debug message indicating that a new incoming command connection was
+detected and a dedicated socket was opened for that connection.
+
+% COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL Encountered error %1 while trying to gracefully shutdown socket
+This message indicates an error while trying to gracefully shutdown command
+connection. The type of the error is included in the message.
+
+% COMMAND_SOCKET_CONNECTION_TIMEOUT Timeout occurred for connection over socket %1
+This is an informational message that indicates that the timeout has
+occurred for one of the command channel connections. The response
+sent by the server indicates a timeout and is then closed.
+
+% COMMAND_SOCKET_READ Received %1 bytes over command socket %2
+This debug message indicates that specified number of bytes was received
+over command socket identified by specified file descriptor.
+
+% COMMAND_SOCKET_READ_FAIL Encountered error %1 while reading from command socket %2
+This error message indicates that an error was encountered while
+reading from command socket.
+
+% COMMAND_SOCKET_WRITE Sent response of %1 bytes (%2 bytes left to send) over command socket %3
+This debug message indicates that the specified number of bytes was sent
+over command socket identifier by the specified file descriptor.
+
+% COMMAND_SOCKET_WRITE_FAIL Error while writing to command socket %1 : %2
+This error message indicates that an error was encountered while
+attempting to send a response to the command socket.
+
+% COMMAND_WATCH_SOCKET_CLEAR_ERROR watch socket failed to clear: %1
+This error message is issued when the command manager was unable to reset
+the ready status after completing a send. This is a programmatic error
+that should be reported. The command manager may or may not continue
+to operate correctly.
+
+% COMMAND_WATCH_SOCKET_CLOSE_ERROR watch socket failed to close: %1
+This error message is issued when command manager attempted to close the
+socket used for indicating the ready status for send operations. This
+should not have any negative impact on the operation of the command
+manager as it happens when the connection is being terminated.
+
+% COMMAND_WATCH_SOCKET_MARK_READY_ERROR watch socket failed to mark ready: %1
+This error message is issued when the command manager was unable to set
+ready status after scheduling asynchronous send. This is programmatic error
+that should be reported. The command manager may or may not continue
+to operate correctly.
diff --git a/src/lib/config/hooked_command_mgr.cc b/src/lib/config/hooked_command_mgr.cc
new file mode 100644
index 0000000..4674080
--- /dev/null
+++ b/src/lib/config/hooked_command_mgr.cc
@@ -0,0 +1,133 @@
+// Copyright (C) 2017-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <config/hooked_command_mgr.h>
+#include <config/config_log.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+#include <boost/pointer_cast.hpp>
+#include <vector>
+
+using namespace isc::data;
+using namespace isc::hooks;
+
+namespace isc {
+namespace config {
+
+HookedCommandMgr::HookedCommandMgr()
+ : BaseCommandMgr() {
+}
+
+bool
+HookedCommandMgr::delegateCommandToHookLibrary(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd,
+ ElementPtr& answer) {
+
+ ConstElementPtr hook_response;
+ if (HooksManager::commandHandlersPresent(cmd_name)) {
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Set status to normal.
+ callout_handle->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+
+ // Delete previously set arguments.
+ callout_handle->deleteAllArguments();
+
+ ConstElementPtr command = original_cmd ? original_cmd :
+ createCommand(cmd_name, params);
+
+ // And pass it to the hook library.
+ callout_handle->setArgument("command", command);
+ callout_handle->setArgument("response", hook_response);
+
+ HooksManager::callCommandHandlers(cmd_name, *callout_handle);
+
+ // The callouts should set the response.
+ callout_handle->getArgument("response", hook_response);
+
+ answer = boost::const_pointer_cast<Element>(hook_response);
+
+ return (true);
+ }
+
+ return (false);
+}
+
+ConstElementPtr
+HookedCommandMgr::handleCommand(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd) {
+
+ // The 'list-commands' is a special case. Hook libraries do not implement
+ // this command. We determine what commands are supported by the hook
+ // libraries by checking what hook points are present that have callouts
+ // registered.
+ if ((cmd_name != "list-commands")) {
+ ElementPtr hook_response;
+ // Check if there are any hooks libraries to process this command.
+ if (delegateCommandToHookLibrary(cmd_name, params, original_cmd,
+ hook_response)) {
+ // Hooks libraries processed this command so simply return a
+ // result.
+ return (hook_response);
+ }
+
+ }
+
+ // If we're here it means that the callouts weren't called. We need
+ // to handle the command using local Command Manager.
+ ConstElementPtr response = BaseCommandMgr::handleCommand(cmd_name,
+ params,
+ original_cmd);
+
+ // If we're processing 'list-commands' command we may need to include
+ // commands supported by hooks libraries in the response.
+ if (cmd_name == "list-commands") {
+ // Hooks names can be used to decode what commands are supported.
+ const std::vector<std::string>& hooks =
+ ServerHooks::getServerHooksPtr()->getHookNames();
+
+ // Only update the response if there are any hooks present.
+ if (!hooks.empty()) {
+ ElementPtr hooks_commands = Element::createList();
+ for (auto h = hooks.cbegin(); h != hooks.end(); ++h) {
+ // Try to convert hook name to command name. If non-empty
+ // string is returned it means that the hook point may have
+ // command handlers associated with it. Otherwise, it means that
+ // existing hook points are not for command handlers but for
+ // regular callouts.
+ std::string command_name = ServerHooks::hookToCommandName(*h);
+ if (!command_name.empty()) {
+ // Final check: are command handlers registered for this
+ // hook point? If there are no command handlers associated,
+ // it means that the hook library was already unloaded.
+ if (HooksManager::commandHandlersPresent(command_name)) {
+ hooks_commands->add(Element::create(command_name));
+ }
+ }
+ }
+
+ // If there is at least one hook point with command handlers
+ // registered
+ // for it, combine the lists of commands.
+ if (!hooks_commands->empty()) {
+ response = combineCommandsLists(response, createAnswer(CONTROL_RESULT_SUCCESS, hooks_commands));
+ }
+ }
+ }
+
+ return (response);
+}
+
+
+} // end of namespace isc::config
+} // end of namespace isc
diff --git a/src/lib/config/hooked_command_mgr.h b/src/lib/config/hooked_command_mgr.h
new file mode 100644
index 0000000..fc5c258
--- /dev/null
+++ b/src/lib/config/hooked_command_mgr.h
@@ -0,0 +1,91 @@
+// Copyright (C) 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 HOOKED_COMMAND_MGR_H
+#define HOOKED_COMMAND_MGR_H
+
+#include <cc/data.h>
+#include <config/base_command_mgr.h>
+
+namespace isc {
+namespace config {
+
+/// @brief Command Manager which can delegate commands to a hook library.
+///
+/// This class extends @ref BaseCommandMgr with the logic to delegate the
+/// commands to a hook library if the hook library is installed and provides
+/// command handlers for the control API.
+///
+/// The command handlers are registered by a hook library by calling
+/// @ref isc::hooks::LibraryHandle::registerCommandCallout. This call
+/// creates a hook point for this command (if one doesn't exist) and then
+/// registers the specified handler(s). When the @ref HookedCommandMgr
+/// receives a command for processing it calls the
+/// @ref isc::hooks::HooksManager::commandHandlersPresent to check if there
+/// are handlers present for this command. If so, the @ref HookedCommandMgr
+/// calls @ref isc::hooks::HooksManager::callCommandHandlers to process
+/// the command in the hooks libraries. If command handlers are not installed
+/// for this command, the @ref HookedCommandMgr will try to process the
+/// command on its own.
+///
+/// The @ref isc::hooks::CalloutHandle::CalloutNextStep flag setting by the
+/// command handlers does NOT have any influence on the operation of the
+/// @ref HookedCommandMgr, i.e. it will always skip processing command on
+/// its own if the command handlers are present for the given command, even
+/// if the handlers return an error code.
+class HookedCommandMgr : public BaseCommandMgr {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes callout handle used by the Command Manager.
+ HookedCommandMgr();
+
+protected:
+
+ /// @brief Handles the command within the hooks libraries.
+ ///
+ /// This method checks if the hooks libraries are installed which implement
+ /// command handlers for the specified command to be processed. If the
+ /// command handlers are present, this method calls them to create a response
+ /// and then passes the response back within the @c answer argument.
+ ///
+ /// Values of all arguments can be modified by the hook library.
+ ///
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Original command received.
+ /// @param [out] answer Command processing result returned by the hook.
+ ///
+ /// @return Boolean value indicating if any callouts have been executed.
+ bool
+ delegateCommandToHookLibrary(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd,
+ isc::data::ElementPtr& answer);
+
+ /// @brief Handles the command having a given name and arguments.
+ ///
+ /// This method calls @ref HookedCommandMgr::delegateCommandToHookLibrary to
+ /// try to process the command with the hook libraries, if they are installed.
+ ///
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Original command received.
+ ///
+ /// @return Pointer to the const data element representing response
+ /// to a command.
+ virtual isc::data::ConstElementPtr
+ handleCommand(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd);
+
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
new file mode 100644
index 0000000..546c357
--- /dev/null
+++ b/src/lib/config/tests/Makefile.am
@@ -0,0 +1,50 @@
+SUBDIRS = testdata .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\"
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(srcdir)/../../asiolink/testutils/ca\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+DISTCLEANFILES = data_def_unittests_config.h
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = client_connection_unittests.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_SOURCES += command_mgr_unittests.cc
+run_unittests_SOURCES += cmd_http_listener_unittests.cc
+run_unittests_SOURCES += cmd_response_creator_unittests.cc
+run_unittests_SOURCES += cmd_response_creator_factory_unittests.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/config/libkea-cfgclient.la
+run_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.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 += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+run_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/config/tests/Makefile.in b/src/lib/config/tests/Makefile.in
new file mode 100644
index 0000000..8fd3708
--- /dev/null
+++ b/src/lib/config/tests/Makefile.in
@@ -0,0 +1,1108 @@
+# 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/config/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = data_def_unittests_config.h
+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 = client_connection_unittests.cc \
+ run_unittests.cc command_mgr_unittests.cc \
+ cmd_http_listener_unittests.cc \
+ cmd_response_creator_unittests.cc \
+ cmd_response_creator_factory_unittests.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = run_unittests-client_connection_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-command_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-cmd_http_listener_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-cmd_response_creator_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-cmd_response_creator_factory_unittests.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.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__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(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-client_connection_unittests.Po \
+ ./$(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po \
+ ./$(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po \
+ ./$(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po \
+ ./$(DEPDIR)/run_unittests-command_mgr_unittests.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.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 =
+SOURCES = $(run_unittests_SOURCES)
+DIST_SOURCES = $(am__run_unittests_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/data_def_unittests_config.h.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = testdata .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\" \
+ -DTEST_CA_DIR=\"$(srcdir)/../../asiolink/testutils/ca\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+DISTCLEANFILES = data_def_unittests_config.h
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@run_unittests_SOURCES = \
+@HAVE_GTEST_TRUE@ client_connection_unittests.cc \
+@HAVE_GTEST_TRUE@ run_unittests.cc command_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ cmd_http_listener_unittests.cc \
+@HAVE_GTEST_TRUE@ cmd_response_creator_unittests.cc \
+@HAVE_GTEST_TRUE@ cmd_response_creator_factory_unittests.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.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@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_LIBS) $(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/config/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/config/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):
+data_def_unittests_config.h: $(top_builddir)/config.status $(srcdir)/data_def_unittests_config.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+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-client_connection_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-command_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.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-client_connection_unittests.o: client_connection_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-client_connection_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-client_connection_unittests.Tpo -c -o run_unittests-client_connection_unittests.o `test -f 'client_connection_unittests.cc' || echo '$(srcdir)/'`client_connection_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-client_connection_unittests.Tpo $(DEPDIR)/run_unittests-client_connection_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_connection_unittests.cc' object='run_unittests-client_connection_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-client_connection_unittests.o `test -f 'client_connection_unittests.cc' || echo '$(srcdir)/'`client_connection_unittests.cc
+
+run_unittests-client_connection_unittests.obj: client_connection_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-client_connection_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-client_connection_unittests.Tpo -c -o run_unittests-client_connection_unittests.obj `if test -f 'client_connection_unittests.cc'; then $(CYGPATH_W) 'client_connection_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/client_connection_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-client_connection_unittests.Tpo $(DEPDIR)/run_unittests-client_connection_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_connection_unittests.cc' object='run_unittests-client_connection_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-client_connection_unittests.obj `if test -f 'client_connection_unittests.cc'; then $(CYGPATH_W) 'client_connection_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/client_connection_unittests.cc'; fi`
+
+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-command_mgr_unittests.o: command_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_mgr_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-command_mgr_unittests.Tpo -c -o run_unittests-command_mgr_unittests.o `test -f 'command_mgr_unittests.cc' || echo '$(srcdir)/'`command_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_mgr_unittests.Tpo $(DEPDIR)/run_unittests-command_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_mgr_unittests.cc' object='run_unittests-command_mgr_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-command_mgr_unittests.o `test -f 'command_mgr_unittests.cc' || echo '$(srcdir)/'`command_mgr_unittests.cc
+
+run_unittests-command_mgr_unittests.obj: command_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-command_mgr_unittests.Tpo -c -o run_unittests-command_mgr_unittests.obj `if test -f 'command_mgr_unittests.cc'; then $(CYGPATH_W) 'command_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/command_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_mgr_unittests.Tpo $(DEPDIR)/run_unittests-command_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_mgr_unittests.cc' object='run_unittests-command_mgr_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-command_mgr_unittests.obj `if test -f 'command_mgr_unittests.cc'; then $(CYGPATH_W) 'command_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/command_mgr_unittests.cc'; fi`
+
+run_unittests-cmd_http_listener_unittests.o: cmd_http_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_http_listener_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Tpo -c -o run_unittests-cmd_http_listener_unittests.o `test -f 'cmd_http_listener_unittests.cc' || echo '$(srcdir)/'`cmd_http_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Tpo $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_http_listener_unittests.cc' object='run_unittests-cmd_http_listener_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-cmd_http_listener_unittests.o `test -f 'cmd_http_listener_unittests.cc' || echo '$(srcdir)/'`cmd_http_listener_unittests.cc
+
+run_unittests-cmd_http_listener_unittests.obj: cmd_http_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_http_listener_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Tpo -c -o run_unittests-cmd_http_listener_unittests.obj `if test -f 'cmd_http_listener_unittests.cc'; then $(CYGPATH_W) 'cmd_http_listener_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_http_listener_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Tpo $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_http_listener_unittests.cc' object='run_unittests-cmd_http_listener_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-cmd_http_listener_unittests.obj `if test -f 'cmd_http_listener_unittests.cc'; then $(CYGPATH_W) 'cmd_http_listener_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_http_listener_unittests.cc'; fi`
+
+run_unittests-cmd_response_creator_unittests.o: cmd_response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_response_creator_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Tpo -c -o run_unittests-cmd_response_creator_unittests.o `test -f 'cmd_response_creator_unittests.cc' || echo '$(srcdir)/'`cmd_response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Tpo $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_response_creator_unittests.cc' object='run_unittests-cmd_response_creator_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-cmd_response_creator_unittests.o `test -f 'cmd_response_creator_unittests.cc' || echo '$(srcdir)/'`cmd_response_creator_unittests.cc
+
+run_unittests-cmd_response_creator_unittests.obj: cmd_response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_response_creator_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Tpo -c -o run_unittests-cmd_response_creator_unittests.obj `if test -f 'cmd_response_creator_unittests.cc'; then $(CYGPATH_W) 'cmd_response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_response_creator_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Tpo $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_response_creator_unittests.cc' object='run_unittests-cmd_response_creator_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-cmd_response_creator_unittests.obj `if test -f 'cmd_response_creator_unittests.cc'; then $(CYGPATH_W) 'cmd_response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_response_creator_unittests.cc'; fi`
+
+run_unittests-cmd_response_creator_factory_unittests.o: cmd_response_creator_factory_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_response_creator_factory_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Tpo -c -o run_unittests-cmd_response_creator_factory_unittests.o `test -f 'cmd_response_creator_factory_unittests.cc' || echo '$(srcdir)/'`cmd_response_creator_factory_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Tpo $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_response_creator_factory_unittests.cc' object='run_unittests-cmd_response_creator_factory_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-cmd_response_creator_factory_unittests.o `test -f 'cmd_response_creator_factory_unittests.cc' || echo '$(srcdir)/'`cmd_response_creator_factory_unittests.cc
+
+run_unittests-cmd_response_creator_factory_unittests.obj: cmd_response_creator_factory_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_response_creator_factory_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Tpo -c -o run_unittests-cmd_response_creator_factory_unittests.obj `if test -f 'cmd_response_creator_factory_unittests.cc'; then $(CYGPATH_W) 'cmd_response_creator_factory_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_response_creator_factory_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Tpo $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_response_creator_factory_unittests.cc' object='run_unittests-cmd_response_creator_factory_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-cmd_response_creator_factory_unittests.obj `if test -f 'cmd_response_creator_factory_unittests.cc'; then $(CYGPATH_W) 'cmd_response_creator_factory_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_response_creator_factory_unittests.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)
+ -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-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/run_unittests-client_connection_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-command_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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-client_connection_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-command_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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/config/tests/client_connection_unittests.cc b/src/lib/config/tests/client_connection_unittests.cc
new file mode 100644
index 0000000..3ecca82
--- /dev/null
+++ b/src/lib/config/tests/client_connection_unittests.cc
@@ -0,0 +1,183 @@
+// Copyright (C) 2017-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 <testutils/sandbox.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <cc/json_feed.h>
+#include <config/client_connection.h>
+#include <gtest/gtest.h>
+#include <cstdlib>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+
+namespace {
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// Test fixture class for @ref ClientConnection.
+class ClientConnectionTest : public ::testing::Test {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// @brief Constructor.
+ ///
+ /// Removes unix socket descriptor before the test.
+ ClientConnectionTest() :
+ io_service_(),
+ test_socket_(new test::TestServerUnixSocket(io_service_,
+ unixSocketFilePath())) {
+ removeUnixSocketFile();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes unix socket descriptor after the test.
+ virtual ~ClientConnectionTest() {
+ removeUnixSocketFile();
+ }
+
+ /// @brief Returns socket file path.
+ ///
+ /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
+ /// socket file is created in the location pointed to by this variable.
+ /// Otherwise, it is created in the build directory.
+ ///
+ /// The KEA_SOCKET_TEST_DIR is typically used to overcome the problem of
+ /// a system limit on the unix socket file path (usually 102 or 103 characters).
+ /// When Kea build is located in the nested directories with absolute path
+ /// exceeding this limit, the test system should be configured to set
+ /// the KEA_SOCKET_TEST_DIR environmental variable to point to an alternative
+ /// location, e.g. /tmp, with an absolute path length being within the
+ /// allowed range.
+ std::string unixSocketFilePath() {
+ std::string socket_path;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path = std::string(env) + "/test-socket";
+ } else {
+ socket_path = sandbox.join("test-socket");
+ }
+ return (socket_path);
+ }
+
+ /// @brief Removes unix socket descriptor.
+ void removeUnixSocketFile() {
+ static_cast<void>(remove(unixSocketFilePath().c_str()));
+ }
+
+ /// @brief IO service used by the tests.
+ IOService io_service_;
+
+ /// @brief Server side unix socket used in these tests.
+ test::TestServerUnixSocketPtr test_socket_;
+};
+
+// Tests successful transaction: connect, send command and receive a
+// response.
+TEST_F(ClientConnectionTest, success) {
+ // Start timer protecting against test timeouts.
+ test_socket_->startTimer(TEST_TIMEOUT);
+
+ // Start the server.
+ test_socket_->bindServerSocket();
+ test_socket_->generateCustomResponse(2048);
+
+ // Create some valid command.
+ std::string command = "{ \"command\": \"list-commands\" }";
+
+ ClientConnection conn(io_service_);
+
+ // This boolean value will indicate when the callback function is invoked
+ // at the end of the transaction (whether it is successful or unsuccessful).
+ bool handler_invoked = false;
+ conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
+ ClientConnection::ControlCommand(command),
+ [&handler_invoked](const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& feed) {
+ // Indicate that the handler has been called to break from the
+ // while loop below.
+ handler_invoked = true;
+ // The ec should contain no error.
+ ASSERT_FALSE(ec);
+ // The JSONFeed should be present and it should contain a valid
+ // response.
+ ASSERT_TRUE(feed);
+ EXPECT_TRUE(feed->feedOk()) << feed->getErrorMessage();
+ });
+ // Run the connection.
+ while (!handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+// This test checks that a timeout is signalled when the communication
+// takes too long.
+TEST_F(ClientConnectionTest, timeout) {
+ // The server will return only partial JSON response (lacking closing
+ // brace). The client will wait for closing brace and eventually the
+ // connection should time out.
+ test_socket_.reset(new test::TestServerUnixSocket(io_service_,
+ unixSocketFilePath(),
+ "{ \"command\": \"foo\""));
+ test_socket_->startTimer(TEST_TIMEOUT);
+
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Command to be sent to the server.
+ std::string command = "{ \"command\": \"list-commands\" }";
+
+ ClientConnection conn(io_service_);
+
+ // This boolean value will be set to true when the callback is invoked.
+ bool handler_invoked = false;
+ conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
+ ClientConnection::ControlCommand(command),
+ [&handler_invoked](const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& /*feed*/) {
+ // Indicate that the callback has been invoked to break the loop
+ // below.
+ handler_invoked = true;
+ ASSERT_TRUE(ec);
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ }, ClientConnection::Timeout(1000));
+
+ while (!handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+// This test checks that an error is returned when the client is unable
+// to connect to the server.
+TEST_F(ClientConnectionTest, connectionError) {
+ // Create the new connection but do not bind the server socket.
+ // The connection should be refused and an error returned.
+ ClientConnection conn(io_service_);
+
+ std::string command = "{ \"command\": \"list-commands\" }";
+
+ bool handler_invoked = false;
+ conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
+ ClientConnection::ControlCommand(command),
+ [&handler_invoked](const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& /*feed*/) {
+ handler_invoked = true;
+ ASSERT_TRUE(ec);
+ });
+
+ while (!handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/config/tests/cmd_http_listener_unittests.cc b/src/lib/config/tests/cmd_http_listener_unittests.cc
new file mode 100644
index 0000000..0093980
--- /dev/null
+++ b/src/lib/config/tests/cmd_http_listener_unittests.cc
@@ -0,0 +1,980 @@
+// 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/testutils/test_tls.h>
+#include <cc/command_interpreter.h>
+#include <config/cmd_http_listener.h>
+#include <config/command_mgr.h>
+#include <http/response.h>
+#include <http/response_parser.h>
+#include <http/tests/test_http_client.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <thread>
+#include <list>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::asiolink::test;
+using namespace isc::config;
+using namespace isc::data;
+using namespace boost::asio::ip;
+using namespace isc::http;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for @ref CmdHttpListener.
+class CmdHttpListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts, deregisters all commands
+ /// from CommandMgr, and enables multi-threading mode.
+ CmdHttpListenerTest()
+ : listener_(), io_service_(), test_timer_(io_service_),
+ run_io_service_timer_(io_service_), clients_(), num_threads_(),
+ num_clients_(), num_in_progress_(0), num_finished_(0), chunk_size_(0),
+ pause_cnt_(0) {
+ test_timer_.setup(std::bind(&CmdHttpListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+
+ // Deregisters commands.
+ CommandMgr::instance().deregisterAll();
+
+ // Enable multi-threading.
+ MultiThreadingMgr::instance().setMode(true);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes HTTP clients, unregisters commands, disables MT.
+ virtual ~CmdHttpListenerTest() {
+ // Wipe out the listener.
+ listener_.reset();
+
+ // Destroy all remaining clients.
+ for (auto const& client : clients_) {
+ client->close();
+ }
+
+ // Deregisters commands.
+ config::CommandMgr::instance().deregisterAll();
+
+ // Disable multi-threading.
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Constructs a complete HTTP POST given a request body.
+ ///
+ /// @param request_body string containing the desired request body.
+ ///
+ /// @return string containing the constructed POST.
+ std::string buildPostStr(const std::string& request_body) {
+ // Create the command string.
+ std::stringstream ss;
+ ss << "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: "
+ << request_body.size() << "\r\n\r\n"
+ << request_body;
+ return (ss.str());
+ }
+
+ /// @brief Initiates a command via a new HTTP client.
+ ///
+ /// This method creates a TestHttpClient instance, adds the
+ /// client to the list of clients, and starts a request based
+ /// on the given command. The client will run on the main
+ /// thread and be driven by the test's IOService instance.
+ ///
+ /// @param request_body JSON String containing the API command
+ /// to be sent.
+ void startRequest(const std::string& request_body = "{ }") {
+ std::string request_str = buildPostStr(request_body);
+
+ // Instantiate the client.
+ TestHttpClientPtr client(new TestHttpClient(io_service_, SERVER_ADDRESS,
+ SERVER_PORT));
+ // Add it to the list of clients.
+ clients_.push_back(client);
+
+ // Start the request. Note, nothing happens until the IOService runs.
+ client->startRequest(request_str);
+ }
+
+ /// @brief Initiates a "thread" command via a new HTTP client.
+ ///
+ /// This method creates a TestHttpClient instance, adds the
+ /// client to the list of clients, and starts a request based
+ /// on the given command. The client will run on the main
+ /// thread and be driven by the test's IOService instance.
+ ///
+ /// The command has a single argument, "client-ptr". The function creates
+ /// the value for this argument from the pointer address of client instance
+ /// it creates. This argument should be echoed back in the response, along
+ /// with the thread-id of the CmdHttpListener thread which handled the
+ /// command. The response body should look this:
+ ///
+ /// ```
+ /// [ { "arguments": { "client-ptr": "xxxxx", "thread-id": "zzzzz" }, "result": 0} ]
+ /// ```
+ void startThreadCommand() {
+ // Create a new client.
+ TestHttpClientPtr client(new TestHttpClient(io_service_, SERVER_ADDRESS,
+ SERVER_PORT));
+
+ // Construct the "thread" command post including the argument,
+ // "client-ptr", whose value is the stringified pointer to the
+ // newly created client.
+ std::stringstream request_body;
+ request_body << "{\"command\": \"thread\", \"arguments\": { \"client-ptr\": \""
+ << client << "\" } }";
+
+ std::string command = buildPostStr(request_body.str());
+
+ // Add it to the list of clients.
+ clients_.push_back(client);
+
+ // Start the request. Note, nothing happens until the IOService runs.
+ ASSERT_NO_THROW_LOG(client->startRequest(command));
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// We iterate over calls to asio::io_service.run(), until
+ /// all the clients have completed their requests. We do it this way
+ /// because the test clients stop the io_service when they're
+ /// through with a request.
+ ///
+ /// @param request_limit Desired number of requests the function should wait
+ /// to be processed before returning.
+ void runIOService(size_t request_limit = 0) {
+ if (!request_limit) {
+ request_limit = clients_.size();
+ }
+
+ // Loop until the clients are done, an error occurs, or the time runs out.
+ size_t num_done = 0;
+ while (num_done != request_limit) {
+ // Always call restart() before we call run();
+ io_service_.restart();
+
+ // Run until a client stops the service.
+ io_service_.run();
+
+ // If all the clients are done receiving, the test is done.
+ num_done = 0;
+ for (auto const& client : clients_) {
+ if (client->receiveDone()) {
+ ++num_done;
+ }
+ }
+ }
+ }
+
+ /// @brief Create an HttpResponse from a response string.
+ ///
+ /// @param response_str a string containing the whole HTTP
+ /// response received.
+ ///
+ /// @return An HttpResponse constructed from by parsing the
+ /// response string.
+ HttpResponsePtr parseResponse(const std::string response_str) {
+ HttpResponsePtr hr(new HttpResponse());
+ HttpResponseParser parser(*hr);
+ parser.initModel();
+ parser.postBuffer(&response_str[0], response_str.size());
+ parser.poll();
+ if (!parser.httpParseOk()) {
+ isc_throw(Unexpected, "response_str: '" << response_str
+ << "' failed to parse: " << parser.getErrorMessage());
+ }
+
+ return (hr);
+ }
+
+ /// @brief Handler for the 'foo' command.
+ ///
+ /// The command needs no arguments and returns a response
+ /// with a body containing:
+ ///
+ /// "[ { \"arguments\": [ \"bar\" ], \"result\": 0 } ]")
+ ///
+ /// @param command_name Command name, i.e. 'foo'.
+ /// @param command_arguments Command arguments (empty).
+ ///
+ /// @return Returns response with a single string "bar".
+ ConstElementPtr fooCommandHandler(const std::string& /*command_name*/,
+ const ConstElementPtr& /*command_arguments*/) {
+ ElementPtr arguments = Element::createList();
+ arguments->add(Element::create("bar"));
+ return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
+ }
+
+ /// @brief Handler for the 'thread' command.
+ ///
+ /// @param command_name Command name, i.e. 'thread'.
+ /// @param command_arguments Command arguments should contain
+ /// one string element, "client-ptr", whose value is the stringified
+ /// pointer to the client that issued the command.
+ ///
+ /// @return Returns response with map of arguments containing
+ /// a string value 'thread-id': <thread id>
+ ConstElementPtr synchronizedCommandHandler(const std::string& /*command_name*/,
+ const ConstElementPtr& command_arguments) {
+ // If the number of in progress commands is less than the number
+ // of threads, then wait here until we're notified. Otherwise,
+ // notify everyone and finish. The idea is to force each thread
+ // to handle the same number of requests over the course of the
+ // test, making verification reliable.
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ ++num_in_progress_;
+ if (num_in_progress_ == chunk_size_) {
+ num_finished_ = 0;
+ cv_.notify_all();
+ } else {
+ bool ret = cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_in_progress_ == chunk_size_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to start work";
+ }
+ }
+ }
+
+ // Create the map of response arguments.
+ ElementPtr arguments = Element::createMap();
+ // First we echo the client-ptr command argument.
+ ConstElementPtr client_ptr = command_arguments->get("client-ptr");
+ if (!client_ptr) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "missing client-ptr"));
+ }
+
+ arguments->set("client-ptr", client_ptr);
+
+ // Now we add the thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ arguments->set("thread-id", Element::create(ss.str()));
+
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ num_finished_++;
+ if (num_finished_ == chunk_size_) {
+ // We're all done, notify the others and finish.
+ num_in_progress_ = 0;
+ cv_.notify_all();
+ } else {
+ // I'm done but others aren't wait here.
+ bool ret = cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_finished_ == chunk_size_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to finish work";
+ }
+ }
+ }
+
+ EXPECT_THROW(listener_->start(), InvalidOperation);
+ EXPECT_THROW(listener_->pause(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(listener_->resume(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(listener_->stop(), MultiThreadingInvalidOperation);
+
+ // We're done, ship it!
+ return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
+ }
+
+ /// @brief Handler for the 'thread' command.
+ ///
+ /// @param command_name Command name, i.e. 'thread'.
+ /// @param command_arguments Command arguments should contain
+ /// one string element, "client-ptr", whose value is the stringified
+ /// pointer to the client that issued the command.
+ ///
+ /// @return Returns response with map of arguments containing
+ /// a string value 'thread-id': <thread id>
+ ConstElementPtr simpleCommandHandler(const std::string& /*command_name*/,
+ const ConstElementPtr& command_arguments) {
+ // Create the map of response arguments.
+ ElementPtr arguments = Element::createMap();
+ // First we echo the client-ptr command argument.
+ ConstElementPtr client_ptr = command_arguments->get("client-ptr");
+ if (!client_ptr) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "missing client-ptr"));
+ }
+
+ arguments->set("client-ptr", client_ptr);
+
+ // Now we add the thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ arguments->set("thread-id", Element::create(ss.str()));
+
+ // We're done, ship it!
+ return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
+ }
+
+ /// @brief Submits one or more thread commands to a CmdHttpListener.
+ ///
+ /// This function command will create a CmdHttpListener
+ /// with the given number of threads, initiates the given
+ /// number of clients, each requesting the "thread" command,
+ /// and then iteratively runs the test's IOService until all
+ /// the clients have received their responses or an error occurs.
+ ///
+ /// It requires that the number of clients, when greater than the
+ /// number of threads, be a multiple of the number of threads. The
+ /// thread command handler is structured in such a way as to ensure
+ /// (we hope) that each thread handles the same number of commands.
+ ///
+ /// @param num_threads - the number of threads the CmdHttpListener
+ /// should use. Must be greater than 0.
+ /// @param num_clients - the number of clients that should issue the
+ /// thread command. Each client is used to carry out a single thread
+ /// command request. Must be greater than 0 and a multiple of num_threads
+ /// if it is greater than num_threads.
+ void threadListenAndRespond(size_t num_threads, size_t num_clients) {
+ // First we makes sure the parameter rules apply.
+ ASSERT_TRUE(num_threads);
+ ASSERT_TRUE(num_clients);
+ ASSERT_TRUE((num_clients < num_threads) || (num_clients % num_threads == 0));
+
+ num_threads_ = num_threads;
+ num_clients_ = num_clients;
+ chunk_size_ = num_threads_;
+ if (num_clients_ < chunk_size_) {
+ chunk_size_ = num_clients_;
+ }
+
+ // Register the thread command handler.
+ CommandMgr::instance().registerCommand("thread",
+ std::bind(&CmdHttpListenerTest
+ ::synchronizedCommandHandler,
+ this, ph::_1, ph::_2));
+
+ // Create a listener with prescribed number of threads.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, num_threads)));
+ ASSERT_TRUE(listener_);
+
+ // Start it and verify it is running.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), num_threads);
+
+ // Maps the number of clients served by a given thread-id.
+ std::map<std::string, int> clients_per_thread;
+
+ // Initiate the prescribed number of command requests.
+ num_in_progress_ = 0;
+ while (clients_.size() < num_clients) {
+ ASSERT_NO_THROW_LOG(startThreadCommand());
+ }
+
+ // Now we run the client-side IOService until all requests are done,
+ // errors occur or the test times out.
+ ASSERT_NO_FATAL_FAILURE(runIOService());
+
+ // Stop the listener and then verify it has stopped.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Iterate over the clients, checking their outcomes.
+ size_t total_responses = 0;
+ for (auto const& client : clients_) {
+ // Client should have completed its receive successfully.
+ ASSERT_TRUE(client->receiveDone());
+
+ // Client response should not be empty.
+ HttpResponsePtr hr;
+ std::string response_str = client->getResponse();
+ ASSERT_FALSE(response_str.empty());
+
+ // Parse the response into an HttpResponse.
+ ASSERT_NO_THROW_LOG(hr = parseResponse(client->getResponse()));
+
+ // Now we walk the element tree to get the response data. It should look
+ // this:
+ //
+ // [ {
+ // "arguments": { "client-ptr": "xxxxx",
+ // "thread-id": "zzzzz" },
+ // "result": 0
+ // } ]
+ //
+ // First we turn it into an Element tree.
+ std::string body_str = hr->getBody();
+ ConstElementPtr body;
+ ASSERT_NO_THROW_LOG(body = Element::fromJSON(hr->getBody()));
+
+ // Outermost is a list, since we're emulating agent responses.
+ ASSERT_EQ(body->getType(), Element::list);
+ ASSERT_EQ(body->size(), 1);
+
+ // Answer should be a map containing "arguments" and "results".
+ ConstElementPtr answer = body->get(0);
+ ASSERT_EQ(answer->getType(), Element::map);
+
+ // "result" should be 0.
+ ConstElementPtr result = answer->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(result->getType(), Element::integer);
+ ASSERT_EQ(result->intValue(), 0);
+
+ // "arguments" is a map containing "client-ptr" and "thread-id".
+ ConstElementPtr arguments = answer->get("arguments");
+ ASSERT_TRUE(arguments);
+ ASSERT_EQ(arguments->getType(), Element::map);
+
+ // "client-ptr" is a string.
+ ConstElementPtr client_ptr = arguments->get("client-ptr");
+ ASSERT_TRUE(client_ptr);
+ ASSERT_EQ(client_ptr->getType(), Element::string);
+
+ // "thread-id" is a string.
+ ConstElementPtr thread_id = arguments->get("thread-id");
+ ASSERT_TRUE(thread_id);
+ ASSERT_EQ(thread_id->getType(), Element::string);
+ std::string thread_id_str = thread_id->stringValue();
+
+ // Make sure the response received was for this client.
+ std::stringstream ss;
+ ss << client;
+ ASSERT_EQ(client_ptr->stringValue(), ss.str());
+
+ // Bump the client count for the given thread-id.
+ auto it = clients_per_thread.find(thread_id_str);
+ if (it != clients_per_thread.end()) {
+ clients_per_thread[thread_id_str] = it->second + 1;
+ } else {
+ clients_per_thread[thread_id_str] = 1;
+ }
+
+ ++total_responses;
+ }
+
+ // We should have responses for all our clients.
+ EXPECT_EQ(total_responses, num_clients);
+
+ // Verify we have the expected number of entries in our map.
+ size_t expected_thread_count = (num_clients < num_threads ?
+ num_clients : num_threads);
+
+ ASSERT_EQ(clients_per_thread.size(), expected_thread_count);
+
+ // Each thread-id ought to have handled the same number of clients.
+ for (auto const& it : clients_per_thread) {
+ EXPECT_EQ(it.second, num_clients / clients_per_thread.size())
+ << "thread-id: " << it.first
+ << ", clients: " << it.second << std::endl;
+ }
+ }
+
+ /// @brief Pauses and resumes a CmdHttpListener while it processes command
+ /// requests.
+ ///
+ /// This function command will create a CmdHttpListener
+ /// with the given number of threads, initiates the given
+ /// number of clients, each requesting the "thread" command,
+ /// and then iteratively runs the test's IOService until all
+ /// the clients have received their responses or an error occurs.
+ /// It will pause and resume the listener at intervals governed
+ /// by the given number of pauses.
+ ///
+ /// @param num_threads - the number of threads the CmdHttpListener
+ /// should use. Must be greater than 0.
+ /// @param num_clients - the number of clients that should issue the
+ /// thread command. Each client is used to carry out a single thread
+ /// command request. Must be greater than 0.
+ /// @param num_pauses Desired number of times the listener should be
+ /// paused during the test. Must be greater than 0.
+ void workPauseAndResume(size_t num_threads, size_t num_clients,
+ size_t num_pauses) {
+ // First we makes sure the parameter rules apply.
+ ASSERT_TRUE(num_threads);
+ ASSERT_TRUE(num_clients);
+ ASSERT_TRUE(num_pauses);
+ num_threads_ = num_threads;
+ num_clients_ = num_clients;
+
+ // Register the thread command handler.
+ CommandMgr::instance().registerCommand("thread",
+ std::bind(&CmdHttpListenerTest
+ ::simpleCommandHandler,
+ this, ph::_1, ph::_2));
+
+ // Create a listener with prescribed number of threads.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, num_threads)));
+ ASSERT_TRUE(listener_);
+
+ // Start it and verify it is running.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), num_threads);
+
+ // Initiate the prescribed number of command requests.
+ num_in_progress_ = 0;
+ while (clients_.size() < num_clients) {
+ ASSERT_NO_THROW_LOG(startThreadCommand());
+ }
+
+ // Now we run the client-side IOService until all requests are done,
+ // errors occur or the test times out. We'll pause and resume the
+ // number of times given by num_pauses.
+ size_t num_done = 0;
+ size_t total_requests = clients_.size();
+ while (num_done < total_requests) {
+ // Calculate how many more requests to process before we pause again.
+ // We divide the number of outstanding requests by the number of pauses
+ // and stop after we've done at least that many more requests.
+ size_t request_limit = (pause_cnt_ < num_pauses ?
+ (num_done + ((total_requests - num_done) / num_pauses))
+ : total_requests);
+
+ // Run test IOService until we hit the limit.
+ runIOService(request_limit);
+
+ // If we've done all our pauses we should be through.
+ if (pause_cnt_ == num_pauses) {
+ break;
+ }
+
+ // Pause the client.
+ ASSERT_NO_THROW(listener_->pause());
+ ASSERT_TRUE(listener_->isPaused());
+ ++pause_cnt_;
+
+ // Check our progress.
+ num_done = 0;
+ for (auto const& client : clients_) {
+ if (client->receiveDone()) {
+ ++num_done;
+ }
+ }
+
+ // We should completed at least as many as our
+ // target limit.
+ ASSERT_GE(num_done, request_limit);
+
+ // Resume the listener.
+ ASSERT_NO_THROW(listener_->resume());
+ ASSERT_TRUE(listener_->isRunning());
+ }
+
+ // Stop the listener and then verify it has stopped.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Iterate over the clients, checking their outcomes.
+ size_t total_responses = 0;
+ for (auto const& client : clients_) {
+ // Client should have completed its receive successfully.
+ ASSERT_TRUE(client->receiveDone());
+
+ // Client response should not be empty.
+ HttpResponsePtr hr;
+ std::string response_str = client->getResponse();
+ ASSERT_FALSE(response_str.empty());
+
+ // Parse the response into an HttpResponse.
+ ASSERT_NO_THROW_LOG(hr = parseResponse(client->getResponse()));
+
+ // Now we walk the element tree to get the response data. It should look
+ // this:
+ //
+ // [ {
+ // "arguments": { "client-ptr": "xxxxx",
+ // "thread-id": "zzzzz" },
+ // "result": 0
+ // } ]
+ //
+ // First we turn it into an Element tree.
+ std::string body_str = hr->getBody();
+ ConstElementPtr body;
+ ASSERT_NO_THROW_LOG(body = Element::fromJSON(hr->getBody()));
+
+ // Outermost is a list, since we're emulating agent responses.
+ ASSERT_EQ(body->getType(), Element::list);
+ ASSERT_EQ(body->size(), 1);
+
+ // Answer should be a map containing "arguments" and "results".
+ ConstElementPtr answer = body->get(0);
+ ASSERT_EQ(answer->getType(), Element::map);
+
+ // "result" should be 0.
+ ConstElementPtr result = answer->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(result->getType(), Element::integer);
+ ASSERT_EQ(result->intValue(), 0);
+
+ // "arguments" is a map containing "client-ptr" and "thread-id".
+ ConstElementPtr arguments = answer->get("arguments");
+ ASSERT_TRUE(arguments);
+ ASSERT_EQ(arguments->getType(), Element::map);
+
+ // "client-ptr" is a string.
+ ConstElementPtr client_ptr = arguments->get("client-ptr");
+ ASSERT_TRUE(client_ptr);
+ ASSERT_EQ(client_ptr->getType(), Element::string);
+
+ // "thread-id" is a string.
+ ConstElementPtr thread_id = arguments->get("thread-id");
+ ASSERT_TRUE(thread_id);
+ ASSERT_EQ(thread_id->getType(), Element::string);
+ std::string thread_id_str = thread_id->stringValue();
+
+ // Make sure the response received was for this client.
+ std::stringstream ss;
+ ss << client;
+ ASSERT_EQ(client_ptr->stringValue(), ss.str());
+
+ ++total_responses;
+ }
+
+ // We should have responses for all our clients.
+ EXPECT_EQ(total_responses, num_clients);
+
+ // We should have had the expected number of pauses.
+ if (!num_pauses) {
+ ASSERT_EQ(pause_cnt_, 0);
+ } else {
+ // We allow a range on pauses of +-1.
+ ASSERT_TRUE((num_pauses - 1) <= pause_cnt_ &&
+ (pause_cnt_ <= (num_pauses + 1)))
+ << " num_pauses: " << num_pauses
+ << ", pause_cnt_" << pause_cnt_;
+ }
+ }
+
+ /// @brief CmdHttpListener instance under test.
+ CmdHttpListenerPtr listener_;
+
+ /// @brief IO service used in drive the test and test clients.
+ IOService io_service_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+
+ /// @brief List of client connections.
+ std::list<TestHttpClientPtr> clients_;
+
+ /// @brief Number of threads the listener should use for the test.
+ size_t num_threads_;
+
+ /// @brief Number of client requests to make during the test.
+ size_t num_clients_;
+
+ /// @brief Number of requests currently in progress.
+ size_t num_in_progress_;
+
+ /// @brief Number of requests that have finished.
+ size_t num_finished_;
+
+ /// @brief Chunk size of requests that need to be processed in parallel.
+ ///
+ /// This can either be the number of threads (if the number of requests is
+ /// greater than the number of threads) or the number of requests (if the
+ /// number of threads is greater than the number of requests).
+ size_t chunk_size_;
+
+ /// @brief Mutex used to lock during thread coordination.
+ std::mutex mutex_;
+
+ /// @brief Condition variable used to coordinate threads.
+ std::condition_variable cv_;
+
+ /// @brief Number of times client has been paused during the test.
+ size_t pause_cnt_;
+};
+
+/// Verifies the construction, starting, stopping, pausing, resuming,
+/// and destruction of CmdHttpListener.
+TEST_F(CmdHttpListenerTest, basics) {
+ // Make sure multi-threading is off.
+ MultiThreadingMgr::instance().setMode(false);
+ IOAddress address(SERVER_ADDRESS);
+ uint16_t port = SERVER_PORT;
+
+ // Make sure we can create one.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(address, port)));
+ ASSERT_TRUE(listener_);
+
+ // Verify the getters do what we expect.
+ EXPECT_EQ(listener_->getAddress(), address);
+ EXPECT_EQ(listener_->getPort(), port);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 1);
+ EXPECT_FALSE(listener_->getTlsContext());
+
+ // It should not have an IOService, should not be listening and
+ // should have no threads.
+ ASSERT_FALSE(listener_->getThreadIOService());
+ EXPECT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Verify that we cannot start it when multi-threading is disabled.
+ ASSERT_FALSE(MultiThreadingMgr::instance().getMode());
+ ASSERT_THROW_MSG(listener_->start(), InvalidOperation,
+ "CmdHttpListener cannot be started"
+ " when multi-threading is disabled");
+
+ // It should still not be listening and have no threads.
+ EXPECT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Enable multi-threading.
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Make sure we can start it and it's listening with 1 thread.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Trying to start it again should fail.
+ ASSERT_THROW_MSG(listener_->start(), InvalidOperation,
+ "CmdHttpListener already started!");
+
+ // Stop it and verify we're no longer listening.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+ ASSERT_FALSE(listener_->getThreadIOService());
+
+ // Make sure we can call stop again without problems.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+
+ // We should be able to restart it.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Destroying it should also stop it.
+ // If the test timeouts we know it didn't!
+ ASSERT_NO_THROW_LOG(listener_.reset());
+
+ // Verify we can construct with more than one thread.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(address, port, 4)));
+ ASSERT_NO_THROW_LOG(listener_->start());
+ EXPECT_EQ(listener_->getAddress(), address);
+ EXPECT_EQ(listener_->getPort(), port);
+ EXPECT_EQ(listener_->getThreadCount(), 4);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 4);
+ ASSERT_TRUE(listener_->isRunning());
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Verify we can pause it. We should still be listening, threads intact,
+ // IOService stopped, state set to PAUSED.
+ ASSERT_NO_THROW_LOG(listener_->pause());
+ ASSERT_TRUE(listener_->isPaused());
+ EXPECT_EQ(listener_->getThreadCount(), 4);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 4);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_TRUE(listener_->getThreadIOService()->stopped());
+
+ // Verify we can resume it.
+ ASSERT_NO_THROW_LOG(listener_->resume());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 4);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 4);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Stop it and verify we're no longer listening.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 4);
+ ASSERT_FALSE(listener_->getThreadIOService());
+ EXPECT_TRUE(listener_->isStopped());
+}
+
+// This test verifies that an HTTP connection can be established and used to
+// transmit an HTTP request and receive the response.
+TEST_F(CmdHttpListenerTest, basicListenAndRespond) {
+
+ // Create a listener with 1 thread.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(IOAddress(SERVER_ADDRESS),
+ SERVER_PORT)));
+ ASSERT_TRUE(listener_);
+
+ // Start the listener and verify it's listening with 1 thread.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+
+ // Now let's send a "foo" command. This should create a client, connect
+ // to our listener, post our request and retrieve our reply.
+ ASSERT_NO_THROW(startRequest("{\"command\": \"foo\"}"));
+ ++num_clients_;
+ ASSERT_EQ(num_clients_, clients_.size());
+ ASSERT_NO_THROW(runIOService());
+ TestHttpClientPtr client = clients_.front();
+ ASSERT_TRUE(client);
+
+ // Parse the response into an HttpResponse.
+ HttpResponsePtr hr;
+ ASSERT_NO_THROW_LOG(hr = parseResponse(client->getResponse()));
+
+ // Without a command handler loaded, we should get an unsupported command response.
+ EXPECT_EQ(hr->getBody(), "[ { \"result\": 2, \"text\": \"'foo' command not supported.\" } ]");
+
+ // Now let's register the foo command handler.
+ CommandMgr::instance().registerCommand("foo",
+ std::bind(&CmdHttpListenerTest::fooCommandHandler,
+ this, ph::_1, ph::_2));
+ // Try posting the foo command again.
+ ASSERT_NO_THROW(startRequest("{\"command\": \"foo\"}"));
+ ++num_clients_;
+ ASSERT_EQ(num_clients_, clients_.size());
+ ASSERT_NO_THROW(runIOService());
+ client = clients_.back();
+ ASSERT_TRUE(client);
+
+ // Parse the response.
+ ASSERT_NO_THROW_LOG(hr = parseResponse(client->getResponse()));
+
+ // We should have a response from our command handler.
+ EXPECT_EQ(hr->getBody(), "[ { \"arguments\": [ \"bar\" ], \"result\": 0 } ]");
+
+ // Make sure the listener is still listening.
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+
+ // Stop the listener then verify it has stopped.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+}
+
+// Now we'll run some permutations of the number of listener threads
+// and the number of client requests.
+
+// One thread, one client.
+TEST_F(CmdHttpListenerTest, oneByOne) {
+ size_t num_threads = 1;
+ size_t num_clients = 1;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// One thread, four clients.
+TEST_F(CmdHttpListenerTest, oneByFour) {
+ size_t num_threads = 1;
+ size_t num_clients = 4;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Four threads, one clients.
+TEST_F(CmdHttpListenerTest, fourByOne) {
+ size_t num_threads = 4;
+ size_t num_clients = 1;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Four threads, four clients.
+TEST_F(CmdHttpListenerTest, fourByFour) {
+ size_t num_threads = 4;
+ size_t num_clients = 4;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Four threads, eight clients.
+TEST_F(CmdHttpListenerTest, fourByEight) {
+ size_t num_threads = 4;
+ size_t num_clients = 8;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Six threads, eighteen clients.
+TEST_F(CmdHttpListenerTest, sixByEighteen) {
+ size_t num_threads = 6;
+ size_t num_clients = 18;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Pauses and resumes the listener while it is processing
+// requests.
+TEST_F(CmdHttpListenerTest, pauseAndResume) {
+ size_t num_threads = 6;
+ size_t num_clients = 18;
+ size_t num_pauses = 3;
+ workPauseAndResume(num_threads, num_clients, num_pauses);
+}
+
+// Check if a TLS listener can be created.
+TEST_F(CmdHttpListenerTest, tls) {
+ IOAddress address(SERVER_ADDRESS);
+ uint16_t port = SERVER_PORT;
+ TlsContextPtr context;
+ configServer(context);
+
+ // Make sure we can create the listener.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(address, port, 1, context)));
+ EXPECT_EQ(listener_->getAddress(), address);
+ EXPECT_EQ(listener_->getPort(), port);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 1);
+ EXPECT_EQ(listener_->getTlsContext(), context);
+ EXPECT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Make sure we can start it and it's listening with 1 thread.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Stop it.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 1);
+ ASSERT_FALSE(listener_->getThreadIOService());
+ EXPECT_TRUE(listener_->isStopped());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/config/tests/cmd_response_creator_factory_unittests.cc b/src/lib/config/tests/cmd_response_creator_factory_unittests.cc
new file mode 100644
index 0000000..50f2dd8
--- /dev/null
+++ b/src/lib/config/tests/cmd_response_creator_factory_unittests.cc
@@ -0,0 +1,71 @@
+// 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 <config/cmd_response_creator_factory.h>
+#include <boost/pointer_cast.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc::config;
+
+namespace {
+
+// This test verifies the default factory constructor and
+// the create() method.
+TEST(CmdResponseCreatorFactory, createDefault) {
+ // Create the factory.
+ CmdResponseCreatorFactory factory;
+
+ // Create a response creator.
+ CmdResponseCreatorPtr response1;
+ ASSERT_NO_THROW(response1 = boost::dynamic_pointer_cast<
+ CmdResponseCreator>(factory.create()));
+ ASSERT_TRUE(response1);
+
+ // Agent response emulation should be enabled by default.
+ EXPECT_TRUE(response1->emulateAgentResponse());
+
+ // Authorization configuration should be an empty pointer.
+ EXPECT_FALSE(CmdResponseCreator::http_auth_config_);
+
+ // By default all commands are accepted.
+ EXPECT_TRUE(CmdResponseCreator::command_accept_list_.empty());
+
+ // Invoke create() again.
+ CmdResponseCreatorPtr response2;
+ ASSERT_NO_THROW(response2 = boost::dynamic_pointer_cast<
+ CmdResponseCreator>(factory.create()));
+ ASSERT_TRUE(response2);
+
+ // And it must always return the same object.
+ EXPECT_TRUE(response1 == response2);
+}
+
+// This test verifies that agent response emulation can
+// be turned off.
+TEST(CmdResponseCreatorFactory, createAgentEmulationDisabled) {
+ // Instantiate the factory with agent emulation disabled.
+ CmdResponseCreatorFactory factory(false);
+
+ // Create the response creator.
+ CmdResponseCreatorPtr response;
+ ASSERT_NO_THROW(response = boost::dynamic_pointer_cast<
+ CmdResponseCreator>(factory.create()));
+ ASSERT_TRUE(response);
+
+ // Agent response emulation should be disabled.
+ EXPECT_FALSE(response->emulateAgentResponse());
+
+ // Authorization configuration should be an empty pointer.
+ EXPECT_FALSE(CmdResponseCreator::http_auth_config_);
+
+ // By default all commands are accepted.
+ EXPECT_TRUE(CmdResponseCreator::command_accept_list_.empty());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/config/tests/cmd_response_creator_unittests.cc b/src/lib/config/tests/cmd_response_creator_unittests.cc
new file mode 100644
index 0000000..7ab27c0
--- /dev/null
+++ b/src/lib/config/tests/cmd_response_creator_unittests.cc
@@ -0,0 +1,438 @@
+// 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 <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <config/cmd_response_creator.h>
+#include <http/post_request.h>
+#include <http/post_request_json.h>
+#include <http/response_json.h>
+
+#include <gtest/gtest.h>
+#include <boost/pointer_cast.hpp>
+#include <functional>
+
+using namespace isc;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::http;
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Test fixture class for @ref CmdResponseCreator.
+class CmdResponseCreatorTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates instance of the response creator and uses this instance to
+ /// create "empty" request. It also removes registered commands from the
+ /// command manager.
+ CmdResponseCreatorTest() {
+ // Deregisters commands.
+ config::CommandMgr::instance().deregisterAll();
+ // Register our "foo" command.
+ config::CommandMgr::instance().
+ registerCommand("foo", std::bind(&CmdResponseCreatorTest::fooCommandHandler,
+ this, ph::_1, ph::_2));
+ // Clear class variables.
+ CmdResponseCreator::http_auth_config_.reset();
+ CmdResponseCreator::command_accept_list_.clear();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes registered commands from the command manager.
+ virtual ~CmdResponseCreatorTest() {
+ config::CommandMgr::instance().deregisterAll();
+ CmdResponseCreator::http_auth_config_.reset();
+ CmdResponseCreator::command_accept_list_.clear();
+ }
+
+ /// @brief SetUp function that wraps call to initCreator.
+ ///
+ /// Creates a default CmdResponseCreator and new HttpRequest.
+ virtual void SetUp() {
+ initCreator();
+ }
+
+ /// @brief Creates a new CmdResponseCreator and new HttpRequest.
+ ///
+ /// @param emulate_agent_flag enables/disables agent response emulation
+ /// in the CmdResponsCreator.
+ void initCreator(bool emulate_agent_flag = true) {
+ response_creator_.reset(new CmdResponseCreator(emulate_agent_flag));
+ request_ = response_creator_->createNewHttpRequest();
+ ASSERT_TRUE(request_) << "initCreator failed to create request";
+ }
+
+ /// @brief Fills request context with required data to create new request.
+ ///
+ /// @param request Request which context should be configured.
+ void setBasicContext(const HttpRequestPtr& request) {
+ request->context()->method_ = "POST";
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 1;
+ request->context()->uri_ = "/foo";
+
+ // Content-Type
+ HttpHeaderContext content_type;
+ content_type.name_ = "Content-Type";
+ content_type.value_ = "application/json";
+ request->context()->headers_.push_back(content_type);
+
+ // Content-Length
+ HttpHeaderContext content_length;
+ content_length.name_ = "Content-Length";
+ content_length.value_ = "0";
+ request->context()->headers_.push_back(content_length);
+ }
+
+ /// @brief Test creation of stock response.
+ ///
+ /// @param status_code Status code to be included in the response.
+ /// @param must_contain Text that must be present in the textual
+ /// representation of the generated response.
+ void testStockResponse(const HttpStatusCode& status_code,
+ const string& must_contain) {
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(
+ response = response_creator_->createStockHttpResponse(request_,
+ status_code)
+ );
+ ASSERT_TRUE(response);
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+ // Make sure the response contains the string specified as argument.
+ EXPECT_TRUE(response_json->toString().find(must_contain) != string::npos);
+
+ }
+
+ /// @brief Handler for the 'foo' test command.
+ ///
+ /// @param command_name Command name, i.e. 'foo'.
+ /// @param command_arguments Command arguments (empty).
+ ///
+ /// @return Returns response with a single string "bar".
+ ConstElementPtr fooCommandHandler(const string& /*command_name*/,
+ const ConstElementPtr& /*command_arguments*/) {
+ ElementPtr arguments = Element::createList();
+ arguments->add(Element::create("bar"));
+ return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
+ }
+
+ /// @brief Instance of the response creator.
+ CmdResponseCreatorPtr response_creator_;
+
+ /// @brief Instance of the "empty" request.
+ ///
+ /// The context belonging to this request may be modified by the unit
+ /// tests to verify various scenarios of response creation.
+ HttpRequestPtr request_;
+};
+
+// This test verifies that the created "empty" request has valid type.
+TEST_F(CmdResponseCreatorTest, createNewHttpRequest) {
+ // The request must be of PostHttpRequestJson type.
+ PostHttpRequestJsonPtr request_json = boost::dynamic_pointer_cast<
+ PostHttpRequestJson>(request_);
+ ASSERT_TRUE(request_json);
+}
+
+// Test that HTTP version of stock response is set to 1.0 if the request
+// context doesn't specify any version.
+TEST_F(CmdResponseCreatorTest, createStockHttpResponseNoVersion) {
+ testStockResponse(HttpStatusCode::BAD_REQUEST, "HTTP/1.0 400 Bad Request");
+}
+
+// Test that HTTP version of stock response is set to 1.0 if the request
+// version is higher than 1.1.
+TEST_F(CmdResponseCreatorTest, createStockHttpResponseHighVersion) {
+ request_->context()->http_version_major_ = 2;
+ testStockResponse(HttpStatusCode::REQUEST_TIMEOUT,
+ "HTTP/1.0 408 Request Timeout");
+}
+
+// Test that the server responds with version 1.1 if request version is 1.1.
+TEST_F(CmdResponseCreatorTest, createStockHttpResponseCorrectVersion) {
+ request_->context()->http_version_major_ = 1;
+ request_->context()->http_version_minor_ = 1;
+ testStockResponse(HttpStatusCode::NO_CONTENT, "HTTP/1.1 204 No Content");
+}
+
+// Test successful server response when the client specifies valid command.
+TEST_F(CmdResponseCreatorTest, createDynamicHttpResponse) {
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response should be in a list by default.
+ ASSERT_TRUE(response_creator_->emulateAgentResponse());
+ ASSERT_TRUE(response_json->getBodyAsJson()->getType() == Element::list)
+ << "response is not a list: " << response_json->toString();
+
+ // Response must be successful.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") !=
+ string::npos);
+
+ // Response must contain JSON body with "result" of 0.
+ EXPECT_TRUE(response_json->toString().find("\"result\": 0") !=
+ string::npos);
+}
+
+// Test successful server response without emulating agent response.
+TEST_F(CmdResponseCreatorTest, createDynamicHttpResponseNoEmulation) {
+ // Recreate the response creator setting emulate_agent_response to false;
+ initCreator(false);
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response should be a map that is not enclosed in a list.
+ ASSERT_FALSE(response_creator_->emulateAgentResponse());
+ ASSERT_TRUE(response_json->getBodyAsJson()->getType() == Element::map)
+ << "response is not a map: " << response_json->toString();
+
+ // Response must be successful.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") !=
+ string::npos);
+
+ // Response must contain JSON body with "result" of 0.
+ EXPECT_TRUE(response_json->toString().find("\"result\": 0") !=
+ string::npos);
+}
+
+// This test verifies that Internal Server Error is returned when invalid C++
+// request type is used. This is considered an error in the server logic.
+TEST_F(CmdResponseCreatorTest, createDynamicHttpResponseInvalidType) {
+ PostHttpRequestPtr request(new PostHttpRequest());
+ setBasicContext(request);
+
+ // Body: "list-commands" is natively supported by the command manager.
+ request->context()->body_ = "{ \"command\": \"list-commands\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request->finalize());
+
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response must contain Internal Server Error status code.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 500 Internal Server Error") !=
+ string::npos);
+}
+
+// This test verifies command filtering.
+TEST_F(CmdResponseCreatorTest, filterCommand) {
+ initCreator(false);
+ setBasicContext(request_);
+ // For the log message...
+ request_->setRemote("127.0.0.1");
+
+ HttpResponseJsonPtr response;
+ ConstElementPtr body;
+ unordered_set<string> accept;
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ accept.insert("foo");
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ body = Element::createList();
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ body = Element::createMap();
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ body = createCommand("foo", ConstElementPtr());
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ body = createCommand("bar", ConstElementPtr());
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_TRUE(response);
+ EXPECT_EQ("HTTP/1.1 403 Forbidden", response->toBriefString());
+
+ accept.clear();
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+}
+
+// This test verifies basic HTTP authentication - reject case.
+// Empty case was handled in createDynamicHttpResponseNoEmulation.
+TEST_F(CmdResponseCreatorTest, basicAuthReject) {
+ initCreator(false);
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // Add no basic HTTP authentication.
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Create basic HTTP authentication configuration.
+ CmdResponseCreator::http_auth_config_.reset(new BasicHttpAuthConfig());
+ BasicHttpAuthConfigPtr basic =
+ boost::dynamic_pointer_cast<BasicHttpAuthConfig>(
+ CmdResponseCreator::http_auth_config_);
+ ASSERT_TRUE(basic);
+ EXPECT_NO_THROW(basic->add("test", "", "123\xa3", ""));
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must not be successful.
+ EXPECT_EQ("HTTP/1.1 401 Unauthorized", response->toBriefString());
+}
+
+// This test verifies basic HTTP authentication - accept case.
+// Empty case was handled in createDynamicHttpResponseNoEmulation.
+TEST_F(CmdResponseCreatorTest, basicAuthAccept) {
+ initCreator(false);
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // Add basic HTTP authentication.
+ HttpHeaderContext auth("Authorization", "Basic dGVzdDoxMjPCow==");
+ request_->context()->headers_.push_back(auth);
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Create basic HTTP authentication configuration.
+ CmdResponseCreator::http_auth_config_.reset(new BasicHttpAuthConfig());
+ BasicHttpAuthConfigPtr basic =
+ boost::dynamic_pointer_cast<BasicHttpAuthConfig>(
+ CmdResponseCreator::http_auth_config_);
+ ASSERT_TRUE(basic);
+ EXPECT_NO_THROW(basic->add("test", "", "123\xa3", ""));
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response must be successful.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") !=
+ string::npos);
+
+ // Response must contain JSON body with "result" of 0.
+ EXPECT_TRUE(response_json->toString().find("\"result\": 0") !=
+ string::npos);
+}
+
+// This test verifies command filtering at the HTTP level - reject case.
+TEST_F(CmdResponseCreatorTest, filterCommandReject) {
+ initCreator(false);
+ setBasicContext(request_);
+ // For the log message...
+ request_->setRemote("127.0.0.1");
+
+ // Body: "bar" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"bar\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Add foo in the access list.
+ CmdResponseCreator::command_accept_list_.insert("foo");
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must not be successful.
+ EXPECT_EQ("HTTP/1.1 403 Forbidden", response->toBriefString());
+}
+
+// This test verifies command filtering at the HTTP level - accept case.
+TEST_F(CmdResponseCreatorTest, filterCommandAccept) {
+ initCreator(false);
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Add foo in the access list.
+ CmdResponseCreator::command_accept_list_.insert("foo");
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response must be successful.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") !=
+ string::npos);
+
+ // Response must contain JSON body with "result" of 0.
+ EXPECT_TRUE(response_json->toString().find("\"result\": 0") !=
+ string::npos);
+}
+
+}
diff --git a/src/lib/config/tests/command_mgr_unittests.cc b/src/lib/config/tests/command_mgr_unittests.cc
new file mode 100644
index 0000000..b607d3e
--- /dev/null
+++ b/src/lib/config/tests/command_mgr_unittests.cc
@@ -0,0 +1,570 @@
+// 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 <gtest/gtest.h>
+
+#include <testutils/sandbox.h>
+#include <asiolink/io_service.h>
+#include <config/base_command_mgr.h>
+#include <config/command_mgr.h>
+#include <config/hooked_command_mgr.h>
+#include <cc/command_interpreter.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
+#include <hooks/library_handle.h>
+#include <string>
+#include <vector>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace std;
+
+// Test class for Command Manager
+class CommandMgrTest : public ::testing::Test {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// Default constructor
+ CommandMgrTest()
+ : io_service_(new IOService()) {
+
+ CommandMgr::instance().setIOService(io_service_);
+
+ handler_name_ = "";
+ handler_params_ = ElementPtr();
+ handler_called_ = false;
+ callout_name_ = "";
+ callout_argument_names_.clear();
+ std::string processed_log_ = "";
+
+ CommandMgr::instance().deregisterAll();
+ CommandMgr::instance().closeCommandSocket();
+
+ resetCalloutIndicators();
+ }
+
+ /// Default destructor
+ virtual ~CommandMgrTest() {
+ CommandMgr::instance().deregisterAll();
+ CommandMgr::instance().closeCommandSocket();
+ resetCalloutIndicators();
+ }
+
+ /// @brief Returns socket path (using either hardcoded path or env variable)
+ /// @return path to the unix socket
+ std::string getSocketPath() {
+ std::string socket_path;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path = std::string(env) + "/test-socket";
+ } else {
+ socket_path = sandbox.join("test-socket");
+ }
+ return (socket_path);
+ }
+
+ /// @brief Resets indicators related to callout invocation.
+ ///
+ /// It also removes any registered callouts.
+ static void resetCalloutIndicators() {
+ callout_name_ = "";
+ callout_argument_names_.clear();
+
+ // Iterate over existing hook points and for each of them remove
+ // callouts registered.
+ std::vector<std::string> hooks = ServerHooks::getServerHooksPtr()->getHookNames();
+ for (auto h = hooks.cbegin(); h != hooks.cend(); ++h) {
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(*h);
+ }
+ }
+
+ /// @brief A simple command handler that always returns an eror
+ static ConstElementPtr my_handler(const std::string& name,
+ const ConstElementPtr& params) {
+
+ handler_name_ = name;
+ handler_params_ = params;
+ handler_called_ = true;
+
+ return (createAnswer(123, "test error message"));
+ }
+
+ /// @brief Test callback which stores callout name and passed arguments and
+ /// which handles the command.
+ ///
+ /// @param callout_handle Handle passed by the hooks framework.
+ /// @return Always 0.
+ static int
+ hook_lib_callout(CalloutHandle& callout_handle) {
+ callout_name_ = "hook_lib_callout";
+
+ ConstElementPtr command;
+ callout_handle.getArgument("command", command);
+
+ ConstElementPtr arg;
+ std::string command_name = parseCommand(arg, command);
+
+ callout_handle.setArgument("response",
+ createAnswer(234, "text generated by hook handler"));
+
+ callout_argument_names_ = callout_handle.getArgumentNames();
+ // Sort arguments alphabetically, so as we can access them on
+ // expected positions and verify.
+ std::sort(callout_argument_names_.begin(), callout_argument_names_.end());
+ return (0);
+ }
+
+ /// @brief Test callback which stores callout name and passed arguments and
+ /// which handles the command.
+ ///
+ /// @param callout_handle Handle passed by the hooks framework.
+ /// @return Always 0.
+ static int
+ command_processed_callout(CalloutHandle& callout_handle) {
+ callout_name_ = "command_processed_handler";
+
+ std::string name;
+ callout_handle.getArgument("name", name);
+
+ ConstElementPtr arguments;
+ callout_handle.getArgument("arguments", arguments);
+
+ ConstElementPtr response;
+ callout_handle.getArgument("response", response);
+ std::ostringstream os;
+ os << name << ":" << arguments->str() << ":" << response->str();
+ processed_log_ = os.str();
+
+ if (name == "change-response") {
+ callout_handle.setArgument("response",
+ createAnswer(777, "replaced response text"));
+ }
+
+ return (0);
+ }
+
+ /// @brief IO service used by these tests.
+ IOServicePtr io_service_;
+
+ /// @brief Name of the command (used in my_handler)
+ static std::string handler_name_;
+
+ /// @brief Parameters passed to the handler (used in my_handler)
+ static ConstElementPtr handler_params_;
+
+ /// @brief Indicates whether my_handler was called
+ static bool handler_called_;
+
+ /// @brief Holds invoked callout name.
+ static std::string callout_name_;
+
+ /// @brief Holds a list of arguments passed to the callout.
+ static std::vector<std::string> callout_argument_names_;
+
+ /// @brief Holds the generated command process log
+ static std::string processed_log_;
+};
+
+/// Name passed to the handler (used in my_handler)
+std::string CommandMgrTest::handler_name_("");
+
+/// Parameters passed to the handler (used in my_handler)
+ConstElementPtr CommandMgrTest::handler_params_;
+
+/// Indicates whether my_handler was called
+bool CommandMgrTest::handler_called_(false);
+
+/// Holds invoked callout name.
+std::string CommandMgrTest::callout_name_("");
+
+/// @brief Holds the generated command process log
+std::string CommandMgrTest::processed_log_;
+
+/// Holds a list of arguments passed to the callout.
+std::vector<std::string> CommandMgrTest::callout_argument_names_;
+
+// Test checks whether the internal command 'list-commands'
+// is working properly.
+TEST_F(CommandMgrTest, listCommandsEmpty) {
+
+ ConstElementPtr command = createCommand("list-commands");
+
+ ConstElementPtr answer;
+
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ ASSERT_TRUE(answer);
+
+ EXPECT_EQ("{ \"arguments\": [ \"list-commands\" ], \"result\": 0 }",
+ answer->str());
+}
+
+// Test checks whether calling a bogus command is handled properly.
+TEST_F(CommandMgrTest, bogusCommand) {
+
+ ConstElementPtr command = createCommand("no-such-command");
+
+ ConstElementPtr answer;
+
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // Make sure the status code is non-zero
+ ASSERT_TRUE(answer);
+ int status_code;
+ parseAnswer(status_code, answer);
+ EXPECT_EQ(CONTROL_RESULT_COMMAND_UNSUPPORTED, status_code);
+}
+
+// Test checks whether handlers installation is sanitized. In particular,
+// whether NULL handler and attempt to install handlers for the same
+// command twice are rejected.
+TEST_F(CommandMgrTest, handlerInstall) {
+
+ // Check that it's not allowed to install NULL pointer instead of a real
+ // command.
+ EXPECT_THROW(CommandMgr::instance().registerCommand("my-command", 0),
+ InvalidCommandHandler);
+
+ // This registration should succeed.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Check that it's not possible to install handlers for the same
+ // command twice.
+ EXPECT_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler), InvalidCommandName);
+}
+
+// Test checks whether the internal list-commands command is working
+// correctly. Also, checks installation and deinstallation of other
+// command handlers.
+TEST_F(CommandMgrTest, listCommands) {
+
+ // Let's install two custom commands.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("make-a-coffee",
+ my_handler));
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("do-the-dishes",
+ my_handler));
+
+ // And then run 'list-commands'
+ ConstElementPtr list_all = createCommand("list-commands");
+ ConstElementPtr answer;
+
+ // Now check that the command is returned by list-commands
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_all));
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"arguments\": [ \"do-the-dishes\", \"list-commands\", "
+ "\"make-a-coffee\" ], \"result\": 0 }", answer->str());
+
+ // Now unregister one command
+ EXPECT_NO_THROW(CommandMgr::instance().deregisterCommand("do-the-dishes"));
+
+ // Now check that the command is returned by list-commands
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_all));
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"arguments\": [ \"list-commands\", "
+ "\"make-a-coffee\" ], \"result\": 0 }", answer->str());
+
+ // Now test deregistration. It should work the first time.
+ EXPECT_NO_THROW(CommandMgr::instance().deregisterCommand("make-a-coffee"));
+
+ // Second time should throw an exception as the handler is no longer there.
+ EXPECT_THROW(CommandMgr::instance().deregisterCommand("make-a-coffee"),
+ InvalidCommandName);
+
+ // You can't uninstall list-commands as it's the internal handler.
+ // It always must be there.
+ EXPECT_THROW(CommandMgr::instance().deregisterCommand("list-commands"),
+ InvalidCommandName);
+
+ // Attempt to register a handler for existing command should fail.
+ EXPECT_THROW(CommandMgr::instance().registerCommand("list-commands",
+ my_handler), InvalidCommandName);
+}
+
+// Test checks whether deregisterAll method uninstalls all handlers,
+// except list-commands.
+TEST_F(CommandMgrTest, deregisterAll) {
+
+ // Install a couple handlers.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command1",
+ my_handler));
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command2",
+ my_handler));
+
+ EXPECT_NO_THROW(CommandMgr::instance().deregisterAll());
+
+ ConstElementPtr answer;
+ EXPECT_NO_THROW(answer = CommandMgr::instance()
+ .processCommand(createCommand("list-commands")));
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"arguments\": [ \"list-commands\" ], \"result\": 0 }",
+ answer->str());
+}
+
+// Test checks whether a command handler can be installed and then
+// runs through processCommand to check that it's indeed called.
+TEST_F(CommandMgrTest, processCommand) {
+ // Install my handler
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Now tell CommandMgr to process a command 'my-command' with the
+ // specified parameter.
+ ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+ ConstElementPtr command = createCommand("my-command", my_params);
+ ConstElementPtr answer;
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ // my_handler remembers all passed parameters and returns status code of 123.
+ ConstElementPtr answer_arg;
+ int status_code;
+ // Check that the returned status code is correct.
+ EXPECT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+ EXPECT_EQ(123, status_code);
+
+ // Check that the parameters passed are correct.
+ EXPECT_EQ(true, handler_called_);
+ EXPECT_EQ("my-command", handler_name_);
+ ASSERT_TRUE(handler_params_);
+ EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", handler_params_->str());
+
+ // Command handlers not installed so expecting that callouts weren't
+ // called.
+ EXPECT_TRUE(callout_name_.empty());
+}
+
+// Verify that processing a command can be delegated to a hook library.
+TEST_F(CommandMgrTest, delegateProcessCommand) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
+ "my-command", hook_lib_callout);
+
+ // Install local handler
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Now tell CommandMgr to process a command 'my-command' with the
+ // specified parameter.
+ ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+ ConstElementPtr command = createCommand("my-command", my_params);
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ // Local handler shouldn't be called because the command is handled by the
+ // hook library.
+ ASSERT_FALSE(handler_called_);
+
+ // Returned status should be unique for the hook library.
+ ConstElementPtr answer_arg;
+ int status_code;
+ ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+ EXPECT_EQ(234, status_code);
+
+ EXPECT_EQ("hook_lib_callout", callout_name_);
+
+ // Check that the appropriate arguments have been set. Include the
+ // 'response' which should have been set by the callout.
+ ASSERT_EQ(2, callout_argument_names_.size());
+ EXPECT_EQ("command", callout_argument_names_[0]);
+ EXPECT_EQ("response", callout_argument_names_[1]);
+}
+
+// Verify that 'list-command' command returns combined list of supported
+// commands from hook library and from the Kea Command Manager.
+TEST_F(CommandMgrTest, delegateListCommands) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
+ "my-command", hook_lib_callout);
+
+ // Create my-command-bis which is unique for the local Command Manager,
+ // i.e. not supported by the hook library. This command should also
+ // be returned as a result of processing 'list-commands'.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command-bis",
+ my_handler));
+
+ // Process command. The command should be routed to the hook library
+ // and the hook library should return the commands it supports.
+ ConstElementPtr command = createCommand("list-commands");
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ ConstElementPtr answer_arg;
+ int status_code;
+ ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+ EXPECT_EQ(0, status_code);
+
+ // The hook library supports: my-command and list-commands commands. The
+ // local Command Manager supports list-commands and my-command-bis. The
+ // combined list should include 3 unique commands.
+ const std::vector<ElementPtr>& commands_list = answer_arg->listValue();
+ ASSERT_EQ(3, commands_list.size());
+ std::vector<std::string> command_names_list;
+ for (auto cmd = commands_list.cbegin(); cmd != commands_list.cend();
+ ++cmd) {
+ command_names_list.push_back((*cmd)->stringValue());
+ }
+ std::sort(command_names_list.begin(), command_names_list.end());
+ EXPECT_EQ("list-commands", command_names_list[0]);
+ EXPECT_EQ("my-command", command_names_list[1]);
+ EXPECT_EQ("my-command-bis", command_names_list[2]);
+}
+
+// This test verifies that a Unix socket can be opened properly and that input
+// parameters (socket-type and socket-name) are verified.
+TEST_F(CommandMgrTest, unixCreate) {
+ // Null pointer is obviously a bad idea.
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(ConstElementPtr()),
+ isc::config::BadSocketInfo);
+
+ // So is passing no parameters.
+ ElementPtr socket_info = Element::createMap();
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ // We don't support ipx sockets
+ socket_info->set("socket-type", Element::create("ipx"));
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ socket_info->set("socket-type", Element::create("unix"));
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ socket_info->set("socket-name", Element::create(getSocketPath()));
+ EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
+
+ // It should be possible to close the socket.
+ EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
+}
+
+// This test checks that when unix path is too long, the socket cannot be opened.
+TEST_F(CommandMgrTest, unixCreateTooLong) {
+ ElementPtr socket_info = Element::fromJSON("{ \"socket-type\": \"unix\","
+ "\"socket-name\": \"/tmp/toolongtoolongtoolongtoolongtoolongtoolong"
+ "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"
+ "\" }");
+
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ SocketError);
+}
+
+// This test verifies that a registered callout for the command_processed
+// hookpoint is invoked and passed the correct information.
+TEST_F(CommandMgrTest, commandProcessedHook) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "command_processed", command_processed_callout);
+
+ // Install local handler
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Now tell CommandMgr to process a command 'my-command' with the
+ // specified parameter.
+ ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+ ConstElementPtr command = createCommand("my-command", my_params);
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ // Local handler should be called
+ ASSERT_TRUE(handler_called_);
+
+ // Verify that the response came through intact
+ EXPECT_EQ("{ \"result\": 123, \"text\": \"test error message\" }",
+ answer->str());
+
+ // Make sure invoked the command_processed callout
+ EXPECT_EQ("command_processed_handler", callout_name_);
+
+ // Verify the callout could extract all the context arguments
+ EXPECT_EQ("my-command:[ \"just\", \"some\", \"data\" ]:"
+ "{ \"result\": 123, \"text\": \"test error message\" }",
+ processed_log_);
+}
+
+// This test verifies that a registered callout for the command_processed
+// hookpoint is invoked and can replace the command response content.
+TEST_F(CommandMgrTest, commandProcessedHookReplaceResponse) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "command_processed", command_processed_callout);
+
+ // Install local handler
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Now tell CommandMgr to process a command 'my-command' with the
+ // specified parameter.
+ ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+ ConstElementPtr command = createCommand("change-response", my_params);
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ // Local handler should not have been called, command isn't recognized
+ ASSERT_FALSE(handler_called_);
+
+ // Verify that we overrode response came
+ EXPECT_EQ("{ \"result\": 777, \"text\": \"replaced response text\" }",
+ answer->str());
+
+ // Make sure invoked the command_processed callout
+ EXPECT_EQ("command_processed_handler", callout_name_);
+
+ // Verify the callout could extract all the context arguments
+ EXPECT_EQ("change-response:[ \"just\", \"some\", \"data\" ]:"
+ "{ \"result\": 2, \"text\": \"'change-response' command not supported.\" }",
+ processed_log_);
+}
+
+// Verifies that a socket cannot be concurrently opened more than once.
+TEST_F(CommandMgrTest, exclusiveOpen) {
+ // Pass in valid parameters.
+ ElementPtr socket_info = Element::createMap();
+ socket_info->set("socket-type", Element::create("unix"));
+ socket_info->set("socket-name", Element::create(getSocketPath()));
+
+ EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
+
+ // Should not be able to open it twice.
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::SocketError);
+
+ // Now let's close it.
+ EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
+
+ // Should be able to re-open it now.
+ EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
+
+ // Now let's close it.
+ EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
+}
diff --git a/src/lib/config/tests/data_def_unittests_config.h.in b/src/lib/config/tests/data_def_unittests_config.h.in
new file mode 100644
index 0000000..0837f84
--- /dev/null
+++ b/src/lib/config/tests/data_def_unittests_config.h.in
@@ -0,0 +1,7 @@
+// 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/.
+
+#define TEST_DATA_PATH "@abs_srcdir@/testdata"
diff --git a/src/lib/config/tests/run_unittests.cc b/src/lib/config/tests/run_unittests.cc
new file mode 100644
index 0000000..fc385f9
--- /dev/null
+++ b/src/lib/config/tests/run_unittests.cc
@@ -0,0 +1,18 @@
+// 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/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+#include <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/config/tests/testdata/Makefile.am b/src/lib/config/tests/testdata/Makefile.am
new file mode 100644
index 0000000..641a142
--- /dev/null
+++ b/src/lib/config/tests/testdata/Makefile.am
@@ -0,0 +1,59 @@
+EXTRA_DIST = data22_1.data
+EXTRA_DIST += data22_2.data
+EXTRA_DIST += data22_3.data
+EXTRA_DIST += data22_4.data
+EXTRA_DIST += data22_5.data
+EXTRA_DIST += data22_6.data
+EXTRA_DIST += data22_7.data
+EXTRA_DIST += data22_8.data
+EXTRA_DIST += data22_9.data
+EXTRA_DIST += data22_10.data
+EXTRA_DIST += data32_1.data
+EXTRA_DIST += data32_2.data
+EXTRA_DIST += data32_3.data
+EXTRA_DIST += data33_1.data
+EXTRA_DIST += data33_2.data
+EXTRA_DIST += data41_1.data
+EXTRA_DIST += data41_2.data
+EXTRA_DIST += spec1.spec
+EXTRA_DIST += spec2.spec
+EXTRA_DIST += spec3.spec
+EXTRA_DIST += spec4.spec
+EXTRA_DIST += spec5.spec
+EXTRA_DIST += spec6.spec
+EXTRA_DIST += spec7.spec
+EXTRA_DIST += spec8.spec
+EXTRA_DIST += spec9.spec
+EXTRA_DIST += spec10.spec
+EXTRA_DIST += spec11.spec
+EXTRA_DIST += spec12.spec
+EXTRA_DIST += spec13.spec
+EXTRA_DIST += spec14.spec
+EXTRA_DIST += spec15.spec
+EXTRA_DIST += spec16.spec
+EXTRA_DIST += spec17.spec
+EXTRA_DIST += spec18.spec
+EXTRA_DIST += spec19.spec
+EXTRA_DIST += spec20.spec
+EXTRA_DIST += spec21.spec
+EXTRA_DIST += spec22.spec
+EXTRA_DIST += spec23.spec
+EXTRA_DIST += spec24.spec
+EXTRA_DIST += spec25.spec
+EXTRA_DIST += spec26.spec
+EXTRA_DIST += spec27.spec
+EXTRA_DIST += spec28.spec
+EXTRA_DIST += spec29.spec
+EXTRA_DIST += spec30.spec
+EXTRA_DIST += spec31.spec
+EXTRA_DIST += spec32.spec
+EXTRA_DIST += spec33.spec
+EXTRA_DIST += spec34.spec
+EXTRA_DIST += spec35.spec
+EXTRA_DIST += spec36.spec
+EXTRA_DIST += spec37.spec
+EXTRA_DIST += spec38.spec
+EXTRA_DIST += spec39.spec
+EXTRA_DIST += spec40.spec
+EXTRA_DIST += spec41.spec
+EXTRA_DIST += spec42.spec
diff --git a/src/lib/config/tests/testdata/Makefile.in b/src/lib/config/tests/testdata/Makefile.in
new file mode 100644
index 0000000..9bbe3e7
--- /dev/null
+++ b/src/lib/config/tests/testdata/Makefile.in
@@ -0,0 +1,560 @@
+# 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/config/tests/testdata
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_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
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = data22_1.data data22_2.data data22_3.data data22_4.data \
+ data22_5.data data22_6.data data22_7.data data22_8.data \
+ data22_9.data data22_10.data data32_1.data data32_2.data \
+ data32_3.data data33_1.data data33_2.data data41_1.data \
+ data41_2.data spec1.spec spec2.spec spec3.spec spec4.spec \
+ spec5.spec spec6.spec spec7.spec spec8.spec spec9.spec \
+ spec10.spec spec11.spec spec12.spec spec13.spec spec14.spec \
+ spec15.spec spec16.spec spec17.spec spec18.spec spec19.spec \
+ spec20.spec spec21.spec spec22.spec spec23.spec spec24.spec \
+ spec25.spec spec26.spec spec27.spec spec28.spec spec29.spec \
+ spec30.spec spec31.spec spec32.spec spec33.spec spec34.spec \
+ spec35.spec spec36.spec spec37.spec spec38.spec spec39.spec \
+ spec40.spec spec41.spec spec42.spec
+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/config/tests/testdata/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/config/tests/testdata/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):
+
+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
+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)
+
+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/config/tests/testdata/data22_1.data b/src/lib/config/tests/testdata/data22_1.data
new file mode 100644
index 0000000..18732c7
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_1.data
@@ -0,0 +1,9 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_10.data b/src/lib/config/tests/testdata/data22_10.data
new file mode 100644
index 0000000..fed4001
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_10.data
@@ -0,0 +1,11 @@
+{
+ "version": 123,
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value8": [ { "a": "d" }, { "a": "e" } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_2.data b/src/lib/config/tests/testdata/data22_2.data
new file mode 100644
index 0000000..c820706
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_2.data
@@ -0,0 +1,8 @@
+{
+ "value1": "asdf",
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true }
+}
diff --git a/src/lib/config/tests/testdata/data22_3.data b/src/lib/config/tests/testdata/data22_3.data
new file mode 100644
index 0000000..7d57dfe
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_3.data
@@ -0,0 +1,8 @@
+{
+ "value1": 1,
+ "value2": false,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true }
+}
diff --git a/src/lib/config/tests/testdata/data22_4.data b/src/lib/config/tests/testdata/data22_4.data
new file mode 100644
index 0000000..52862d5
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_4.data
@@ -0,0 +1,8 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, "a" ],
+ "value6": { "v61": "bar", "v62": true }
+}
diff --git a/src/lib/config/tests/testdata/data22_5.data b/src/lib/config/tests/testdata/data22_5.data
new file mode 100644
index 0000000..43937d6
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_5.data
@@ -0,0 +1,8 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": "Break" }
+}
diff --git a/src/lib/config/tests/testdata/data22_6.data b/src/lib/config/tests/testdata/data22_6.data
new file mode 100644
index 0000000..7b53490
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_6.data
@@ -0,0 +1,10 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value7": [ 1, 2.2, "str", true ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_7.data b/src/lib/config/tests/testdata/data22_7.data
new file mode 100644
index 0000000..98f537b
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_7.data
@@ -0,0 +1,10 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value8": [ { "a": "d" }, { "a": "e" } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_8.data b/src/lib/config/tests/testdata/data22_8.data
new file mode 100644
index 0000000..8bc0aa9
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_8.data
@@ -0,0 +1,10 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value8": [ { "a": "d" }, { "a": 1 } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_9.data b/src/lib/config/tests/testdata/data22_9.data
new file mode 100644
index 0000000..f115194
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_9.data
@@ -0,0 +1,11 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value8": [ { "a": "d" }, { "a": "e" } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } },
+ "value_does_not_exist": 1
+}
diff --git a/src/lib/config/tests/testdata/data32_1.data b/src/lib/config/tests/testdata/data32_1.data
new file mode 100644
index 0000000..5695b52
--- /dev/null
+++ b/src/lib/config/tests/testdata/data32_1.data
@@ -0,0 +1,3 @@
+{
+ "named_set_item": { "foo": 1, "bar": 2 }
+}
diff --git a/src/lib/config/tests/testdata/data32_2.data b/src/lib/config/tests/testdata/data32_2.data
new file mode 100644
index 0000000..d5b9765
--- /dev/null
+++ b/src/lib/config/tests/testdata/data32_2.data
@@ -0,0 +1,3 @@
+{
+ "named_set_item": { "foo": "wrongtype", "bar": 2 }
+}
diff --git a/src/lib/config/tests/testdata/data32_3.data b/src/lib/config/tests/testdata/data32_3.data
new file mode 100644
index 0000000..85f32fe
--- /dev/null
+++ b/src/lib/config/tests/testdata/data32_3.data
@@ -0,0 +1,3 @@
+{
+ "named_set_item": []
+}
diff --git a/src/lib/config/tests/testdata/data33_1.data b/src/lib/config/tests/testdata/data33_1.data
new file mode 100644
index 0000000..429852c
--- /dev/null
+++ b/src/lib/config/tests/testdata/data33_1.data
@@ -0,0 +1,7 @@
+{
+ "dummy_str": "Dummy String",
+ "dummy_int": 118,
+ "dummy_datetime": "2011-05-27T19:42:57Z",
+ "dummy_date": "2011-05-27",
+ "dummy_time": "19:42:57"
+}
diff --git a/src/lib/config/tests/testdata/data33_2.data b/src/lib/config/tests/testdata/data33_2.data
new file mode 100644
index 0000000..eb0615c
--- /dev/null
+++ b/src/lib/config/tests/testdata/data33_2.data
@@ -0,0 +1,7 @@
+{
+ "dummy_str": "Dummy String",
+ "dummy_int": 118,
+ "dummy_datetime": "xxxx",
+ "dummy_date": "xxxx",
+ "dummy_time": "xxxx"
+}
diff --git a/src/lib/config/tests/testdata/data41_1.data b/src/lib/config/tests/testdata/data41_1.data
new file mode 100644
index 0000000..d309854
--- /dev/null
+++ b/src/lib/config/tests/testdata/data41_1.data
@@ -0,0 +1,12 @@
+{
+ "zones": {
+ "example.org": {
+ "queries.tcp": 100,
+ "queries.udp": 200
+ },
+ "example.net": {
+ "queries.tcp": 300,
+ "queries.udp": 400
+ }
+ }
+}
diff --git a/src/lib/config/tests/testdata/data41_2.data b/src/lib/config/tests/testdata/data41_2.data
new file mode 100644
index 0000000..c64e931
--- /dev/null
+++ b/src/lib/config/tests/testdata/data41_2.data
@@ -0,0 +1,16 @@
+{
+ "zones": [
+ {
+ "example.org": {
+ "queries.tcp": 100,
+ "queries.udp": 200
+ }
+ },
+ {
+ "example.net": {
+ "queries.tcp": 300,
+ "queries.udp": 400
+ }
+ }
+ ]
+}
diff --git a/src/lib/config/tests/testdata/spec1.spec b/src/lib/config/tests/testdata/spec1.spec
new file mode 100644
index 0000000..05a3794
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec1.spec
@@ -0,0 +1,6 @@
+{
+ "module_spec": {
+ "module_name": "Spec1"
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec10.spec b/src/lib/config/tests/testdata/spec10.spec
new file mode 100644
index 0000000..d2bac77
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec10.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec11.spec b/src/lib/config/tests/testdata/spec11.spec
new file mode 100644
index 0000000..61cb2c3
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec11.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec12.spec b/src/lib/config/tests/testdata/spec12.spec
new file mode 100644
index 0000000..9083a66
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec12.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec13.spec b/src/lib/config/tests/testdata/spec13.spec
new file mode 100644
index 0000000..c13be46
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec13.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec14.spec b/src/lib/config/tests/testdata/spec14.spec
new file mode 100644
index 0000000..3ce1852
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec14.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec15.spec b/src/lib/config/tests/testdata/spec15.spec
new file mode 100644
index 0000000..a9ef085
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec15.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "badname",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec16.spec b/src/lib/config/tests/testdata/spec16.spec
new file mode 100644
index 0000000..e78bc2e
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec16.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": 1
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec17.spec b/src/lib/config/tests/testdata/spec17.spec
new file mode 100644
index 0000000..74a1c25
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec17.spec
@@ -0,0 +1,17 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_description": "Print the given message to stdout",
+ "command_args": [ {
+ "item_name": "message",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec18.spec b/src/lib/config/tests/testdata/spec18.spec
new file mode 100644
index 0000000..e3854aa
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec18.spec
@@ -0,0 +1,12 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec19.spec b/src/lib/config/tests/testdata/spec19.spec
new file mode 100644
index 0000000..1b3c703
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec19.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout",
+ "command_args": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec2.spec b/src/lib/config/tests/testdata/spec2.spec
new file mode 100644
index 0000000..482c206
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec2.spec
@@ -0,0 +1,83 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1
+ },
+ { "item_name": "item2",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 1.1
+ },
+ { "item_name": "item3",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": true
+ },
+ { "item_name": "item4",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "test"
+ },
+ { "item_name": "item5",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [ "a", "b" ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ },
+ { "item_name": "item6",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "value1",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "default"
+ },
+ { "item_name": "value2",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
+ }
+ ],
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout",
+ "command_args": [ {
+ "item_name": "message",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ },
+ {
+ "command_name": "shutdown",
+ "command_description": "Shut down Kea",
+ "command_args": []
+ }
+ ],
+ "statistics": [
+ {
+ "item_name": "dummy_time",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "Dummy Time",
+ "item_description": "A dummy date time",
+ "item_format": "date-time"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec20.spec b/src/lib/config/tests/testdata/spec20.spec
new file mode 100644
index 0000000..c9d32a7
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec20.spec
@@ -0,0 +1,18 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout",
+ "command_args": [ {
+ "item_name": "message",
+ "item_type": "somethingbad",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec21.spec b/src/lib/config/tests/testdata/spec21.spec
new file mode 100644
index 0000000..0af4302
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec21.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": 1
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec22.spec b/src/lib/config/tests/testdata/spec22.spec
new file mode 100644
index 0000000..687db08
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec22.spec
@@ -0,0 +1,114 @@
+{
+ "module_spec": {
+ "module_name": "Spec22",
+ "config_data": [
+ { "item_name": "value1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 9
+ },
+ { "item_name": "value2",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 9.9
+ },
+ { "item_name": "value3",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ },
+ { "item_name": "value4",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "default_string"
+ },
+ { "item_name": "value5",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [ "a", "b" ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 8
+ }
+ },
+ { "item_name": "value6",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v61",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "def"
+ },
+ { "item_name": "v62",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ }
+ ]
+ },
+ { "item_name": "value7",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [ ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "any",
+ "item_optional": true
+ }
+ },
+ { "item_name": "value8",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [ ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": { "a": "b" },
+ "map_item_spec": [
+ { "item_name": "a",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "empty"
+ }
+ ]
+ }
+ },
+ { "item_name": "value9",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": { "v91": "def", "v92": {} },
+ "map_item_spec": [
+ { "item_name": "v91",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "def"
+ },
+ { "item_name": "v92",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v92a",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Hello"
+ } ,
+ {
+ "item_name": "v92b",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 56176
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec23.spec b/src/lib/config/tests/testdata/spec23.spec
new file mode 100644
index 0000000..6791b6c
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec23.spec
@@ -0,0 +1,18 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout",
+ "command_args": [ {
+ "item_name": "message",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec24.spec b/src/lib/config/tests/testdata/spec24.spec
new file mode 100644
index 0000000..bcb50ba
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec24.spec
@@ -0,0 +1,18 @@
+{
+ "module_spec": {
+ "module_name": "Spec24",
+ "config_data": [
+ { "item_name": "item",
+ "item_type": "list",
+ "item_optional": true,
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec25.spec b/src/lib/config/tests/testdata/spec25.spec
new file mode 100644
index 0000000..6a174d5
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec25.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec25",
+ "module_description": "Just an empty module"
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec26.spec b/src/lib/config/tests/testdata/spec26.spec
new file mode 100644
index 0000000..27f3c5b
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec26.spec
@@ -0,0 +1,6 @@
+{
+ "module_spec": {
+ "module_name": "Spec26",
+ "module_description": 1
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec27.spec b/src/lib/config/tests/testdata/spec27.spec
new file mode 100644
index 0000000..2c73021
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec27.spec
@@ -0,0 +1,121 @@
+{
+ "module_spec": {
+ "module_name": "Spec27",
+ "commands": [
+ {
+ "command_name": "cmd1",
+ "command_description": "command_for_unittest",
+ "command_args": [
+ {
+ "item_name": "value1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 9
+ },
+ { "item_name": "value2",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 9.9
+ },
+ { "item_name": "value3",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ },
+ { "item_name": "value4",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "default_string"
+ },
+ { "item_name": "value5",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [ "a", "b" ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 8
+ }
+ },
+ { "item_name": "value6",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v61",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "def"
+ },
+ { "item_name": "v62",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ }
+ ]
+ },
+ { "item_name": "value7",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [ ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "any",
+ "item_optional": true
+ }
+ },
+ { "item_name": "value8",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [ ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": { "a": "b" },
+ "map_item_spec": [
+ { "item_name": "a",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "empty"
+ }
+ ]
+ }
+ },
+ { "item_name": "value9",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v91",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "def"
+ },
+ { "item_name": "v92",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v92a",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Hello"
+ } ,
+ {
+ "item_name": "v92b",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 56176
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec28.spec b/src/lib/config/tests/testdata/spec28.spec
new file mode 100644
index 0000000..cc83acb
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec28.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec28",
+ 'commands': [ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec29.spec b/src/lib/config/tests/testdata/spec29.spec
new file mode 100644
index 0000000..c7ae5f8
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec29.spec
@@ -0,0 +1,35 @@
+{
+ "module_spec": {
+ "module_name": "Spec29",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ],
+ "commands": [
+ {
+ "command_name": "good_command",
+ "command_description": "A good command",
+ "command_args": []
+ },
+ {
+ "command_name": "bad_command",
+ "command_description": "A bad command",
+ "command_args": []
+ },
+ {
+ "command_name": "command_with_arg",
+ "command_description": "A command with an (integer) argument",
+ "command_args": [ {
+ "item_name": "number",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 1
+ } ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec3.spec b/src/lib/config/tests/testdata/spec3.spec
new file mode 100644
index 0000000..b59d949
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec3.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec3",
+ "config_data": [
+ {
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec30.spec b/src/lib/config/tests/testdata/spec30.spec
new file mode 100644
index 0000000..a9e00ad
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec30.spec
@@ -0,0 +1,45 @@
+{
+ "module_spec": {
+ "module_name": "lists",
+ "module_description": "Logging options",
+ "config_data": [
+ {
+ "item_name": "first_list_items",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "first_list_item",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "foo",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "foo"
+ },
+ { "item_name": "second_list_items",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "second_list_item",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "final_element",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "hello"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec31.spec b/src/lib/config/tests/testdata/spec31.spec
new file mode 100644
index 0000000..9eebfd1
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec31.spec
@@ -0,0 +1,63 @@
+{
+ "module_spec": {
+ "module_name": "lists",
+ "module_description": "Logging options",
+ "config_data": [
+ {
+ "item_name": "first_list_items",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "first_list_item",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "foo",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "foo"
+ },
+ { "item_name": "second_list_items",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "second_list_item",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "map_element",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "list1",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "list2",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "number",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1
+ }
+ }
+ }]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec32.spec b/src/lib/config/tests/testdata/spec32.spec
new file mode 100644
index 0000000..2baf1c1
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec32.spec
@@ -0,0 +1,72 @@
+{
+ "module_spec": {
+ "module_name": "Spec32",
+ "config_data": [
+ { "item_name": "named_set_item",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": { "a": 1, "b": 2 },
+ "named_set_item_spec": {
+ "item_name": "named_set_element",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 3
+ }
+ },
+ { "item_name": "named_set_item2",
+ "item_type": "named_set",
+ "item_optional": true,
+ "item_default": { },
+ "named_set_item_spec": {
+ "item_name": "named_set_element",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "first",
+ "item_type": "integer",
+ "item_optional": true
+ },
+ { "item_name": "second",
+ "item_type": "string",
+ "item_optional": true
+ }
+ ]
+ }
+ },
+ { "item_name": "named_set_item3",
+ "item_type": "named_set",
+ "item_optional": true,
+ "item_default": { "values": [ 1, 2, 3 ] },
+ "named_set_item_spec": {
+ "item_name": "named_set_element",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "list_value",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ }
+ },
+ { "item_name": "named_set_item4",
+ "item_type": "named_set",
+ "item_optional": true,
+ "item_default": {},
+ "named_set_item_spec": {
+ "item_name": "named_set_element",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": { "a": 1, "b": 2 },
+ "named_set_item_spec":
+ { "item_name": "named_set_element",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ }
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec33.spec b/src/lib/config/tests/testdata/spec33.spec
new file mode 100644
index 0000000..3002488
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec33.spec
@@ -0,0 +1,50 @@
+{
+ "module_spec": {
+ "module_name": "Spec33",
+ "statistics": [
+ {
+ "item_name": "dummy_str",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Dummy",
+ "item_title": "Dummy String",
+ "item_description": "A dummy string"
+ },
+ {
+ "item_name": "dummy_int",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Dummy Integer",
+ "item_description": "A dummy integer"
+ },
+ {
+ "item_name": "dummy_datetime",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "Dummy DateTime",
+ "item_description": "A dummy datetime",
+ "item_format": "date-time"
+ },
+ {
+ "item_name": "dummy_date",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01",
+ "item_title": "Dummy Date",
+ "item_description": "A dummy date",
+ "item_format": "date"
+ },
+ {
+ "item_name": "dummy_time",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "00:00:00",
+ "item_title": "Dummy Time",
+ "item_description": "A dummy time",
+ "item_format": "time"
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec34.spec b/src/lib/config/tests/testdata/spec34.spec
new file mode 100644
index 0000000..dd1f3ca
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec34.spec
@@ -0,0 +1,14 @@
+{
+ "module_spec": {
+ "module_name": "Spec34",
+ "statistics": [
+ {
+ "item_name": "dummy_str",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Dummy",
+ "item_description": "A dummy string"
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec35.spec b/src/lib/config/tests/testdata/spec35.spec
new file mode 100644
index 0000000..86aaf14
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec35.spec
@@ -0,0 +1,15 @@
+{
+ "module_spec": {
+ "module_name": "Spec35",
+ "statistics": [
+ {
+ "item_name": "dummy_str",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Dummy",
+ "item_title": "Dummy String"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec36.spec b/src/lib/config/tests/testdata/spec36.spec
new file mode 100644
index 0000000..fb9ce26
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec36.spec
@@ -0,0 +1,17 @@
+{
+ "module_spec": {
+ "module_name": "Spec36",
+ "statistics": [
+ {
+ "item_name": "dummy_str",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Dummy",
+ "item_title": "Dummy String",
+ "item_description": "A dummy string",
+ "item_format": "dummy"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec37.spec b/src/lib/config/tests/testdata/spec37.spec
new file mode 100644
index 0000000..bc444d1
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec37.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec37",
+ "statistics": 8
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec38.spec b/src/lib/config/tests/testdata/spec38.spec
new file mode 100644
index 0000000..1892e88
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec38.spec
@@ -0,0 +1,17 @@
+{
+ "module_spec": {
+ "module_name": "Spec38",
+ "statistics": [
+ {
+ "item_name": "dummy_datetime",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "11",
+ "item_title": "Dummy DateTime",
+ "item_description": "A dummy datetime",
+ "item_format": "date-time"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec39.spec b/src/lib/config/tests/testdata/spec39.spec
new file mode 100644
index 0000000..1f72319
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec39.spec
@@ -0,0 +1,21 @@
+{
+ "module_spec": {
+ "module_name": "Spec39",
+ "config_data": [
+ { "item_name": "list",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "list_item",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ }
+ }
+ ],
+ "commands": [],
+ "statistics": []
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec4.spec b/src/lib/config/tests/testdata/spec4.spec
new file mode 100644
index 0000000..80d9b22
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec4.spec
@@ -0,0 +1,12 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec40.spec b/src/lib/config/tests/testdata/spec40.spec
new file mode 100644
index 0000000..6fbec10
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec40.spec
@@ -0,0 +1,21 @@
+{
+ "module_spec": {
+ "module_name": "Spec40",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "any",
+ "item_optional": false,
+ "item_default": "asdf"
+ },
+ { "item_name": "item2",
+ "item_type": "any",
+ "item_optional": true
+ },
+ { "item_name": "item3",
+ "item_type": "any",
+ "item_optional": true,
+ "item_default": null
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec41.spec b/src/lib/config/tests/testdata/spec41.spec
new file mode 100644
index 0000000..1c57cfe
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec41.spec
@@ -0,0 +1,35 @@
+{
+ "module_spec": {
+ "module_name": "Spec40",
+ "statistics": [
+ {
+ "item_name": "zones",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": { },
+ "item_title": "Dummy name set",
+ "item_description": "A dummy name set",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": { },
+ "map_item_spec": [
+ {
+ "item_name": "queries.tcp",
+ "item_optional": false,
+ "item_type": "integer",
+ "item_default": 0
+ },
+ {
+ "item_name": "queries.udp",
+ "item_optional": false,
+ "item_type": "integer",
+ "item_default": 0
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec42.spec b/src/lib/config/tests/testdata/spec42.spec
new file mode 100644
index 0000000..d822465
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec42.spec
@@ -0,0 +1,17 @@
+{
+ "module_spec": {
+ "module_name": "Spec42",
+ "config_data": [
+ { "item_name": "list_item",
+ "item_type": "list",
+ "item_optional": true,
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec5.spec b/src/lib/config/tests/testdata/spec5.spec
new file mode 100644
index 0000000..515424a
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec5.spec
@@ -0,0 +1,12 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec6.spec b/src/lib/config/tests/testdata/spec6.spec
new file mode 100644
index 0000000..631d882
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec6.spec
@@ -0,0 +1,12 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_optional": false
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec7.spec b/src/lib/config/tests/testdata/spec7.spec
new file mode 100644
index 0000000..42f8b7a
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec7.spec
@@ -0,0 +1,5 @@
+{
+ "module_spec": {
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec8.spec b/src/lib/config/tests/testdata/spec8.spec
new file mode 100644
index 0000000..bfd870e
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec8.spec
@@ -0,0 +1,3 @@
+{
+}
+
diff --git a/src/lib/config/tests/testdata/spec9.spec b/src/lib/config/tests/testdata/spec9.spec
new file mode 100644
index 0000000..21018b8
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec9.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": "asdf"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/timeouts.h b/src/lib/config/timeouts.h
new file mode 100644
index 0000000..c7f889e
--- /dev/null
+++ b/src/lib/config/timeouts.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2018-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 CONFIG_TIMEOUTS_H
+#define CONFIG_TIMEOUTS_H
+
+namespace isc {
+namespace config {
+
+// All timeouts provided below are in milliseconds.
+
+/// @brief Timeout for the DHCP server to receive command over the
+/// unix domain socket.
+constexpr long TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND = 10000;
+
+/// @brief Timeout for the Control Agent to receive command over the
+/// RESTful interface.
+constexpr long TIMEOUT_AGENT_RECEIVE_COMMAND = 10000;
+
+/// @brief Timeout for the idle connection to be closed.
+constexpr long TIMEOUT_AGENT_IDLE_CONNECTION_TIMEOUT = 30000;
+
+/// @brief Timeout for the Control Agent to forward command to a
+/// Kea server, e.g. DHCP server.
+///
+/// This value is high to ensure that the server have enough time
+/// to generate large responses, e.g. dump whole lease database.
+constexpr long TIMEOUT_AGENT_FORWARD_COMMAND = 60000;
+
+/// @brief Timeout for the HTTP clients awaiting a response to a request.
+///
+/// This value is high to ensure that the client waits long enough
+/// for the fulfilling server to generate a response. Specified
+/// milliseconds.
+constexpr long TIMEOUT_DEFAULT_HTTP_CLIENT_REQUEST = 10000;
+
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif // CONFIG_TIMEOUTS_H